use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
+use OCP\DB\Events\AddMissingIndicesEvent;
use OCP\DB\Types;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IDateTimeFormatter;
private $checker;
/** @var LoggerInterface */
private $logger;
+ /** @var IEventDispatcher */
+ private $eventDispatcher;
/** @var EventDispatcherInterface */
private $dispatcher;
/** @var Connection */
IL10N $l10n,
Checker $checker,
LoggerInterface $logger,
+ IEventDispatcher $eventDispatcher,
EventDispatcherInterface $dispatcher,
Connection $db,
ILockingProvider $lockingProvider,
$this->l10n = $l10n;
$this->checker = $checker;
$this->logger = $logger;
+ $this->eventDispatcher = $eventDispatcher;
$this->dispatcher = $dispatcher;
$this->db = $db;
$this->lockingProvider = $lockingProvider;
protected function hasMissingIndexes(): array {
$indexInfo = new MissingIndexInformation();
+
// Dispatch event so apps can also hint for pending index updates if needed
$event = new GenericEvent($indexInfo);
$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
+ $event = new AddMissingIndicesEvent();
+ $this->eventDispatcher->dispatchTyped($event);
+ $missingIndices = $event->getMissingIndices();
+
+ if ($missingIndices !== []) {
+ $schema = new SchemaWrapper(\OCP\Server::get(Connection::class));
+ foreach ($missingIndices as $missingIndex) {
+ if ($schema->hasTable($missingIndex['tableName'])) {
+ $table = $schema->getTable($missingIndex['tableName']);
+ if (!$table->hasIndex($missingIndex['indexName'])) {
+ $indexInfo->addHintForMissingSubject($missingIndex['tableName'], $missingIndex['indexName']);
+ }
+ }
+ }
+ }
+
return $indexInfo->getListOfMissingIndexes();
}
use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IDateTimeFormatter;
private $logger;
/** @var Checker|\PHPUnit\Framework\MockObject\MockObject */
private $checker;
+ /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
+ private $eventDispatcher;
/** @var EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject */
private $dispatcher;
/** @var Connection|\PHPUnit\Framework\MockObject\MockObject */
->willReturnCallback(function ($message, array $replace) {
return vsprintf($message, $replace);
});
+ $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)
->disableOriginalConstructor()->getMock();
$this->checker = $this->getMockBuilder('\OC\IntegrityCheck\Checker')
$this->l10n,
$this->checker,
$this->logger,
+ $this->eventDispatcher,
$this->dispatcher,
$this->db,
$this->lockingProvider,
$this->l10n,
$this->checker,
$this->logger,
+ $this->eventDispatcher,
$this->dispatcher,
$this->db,
$this->lockingProvider,
$this->l10n,
$this->checker,
$this->logger,
+ $this->eventDispatcher,
$this->dispatcher,
$this->db,
$this->lockingProvider,
$this->l10n,
$this->checker,
$this->logger,
+ $this->eventDispatcher,
$this->dispatcher,
$this->db,
$this->lockingProvider,
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
+use OCP\DB\Events\AddMissingIndicesEvent;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
*/
class AddMissingIndices extends Command {
private Connection $connection;
+ private IEventDispatcher $eventDispatcher;
private EventDispatcherInterface $dispatcher;
- public function __construct(Connection $connection, EventDispatcherInterface $dispatcher) {
+ public function __construct(Connection $connection, IEventDispatcher $eventDispatcher, EventDispatcherInterface $dispatcher) {
parent::__construct();
$this->connection = $connection;
+ $this->eventDispatcher = $eventDispatcher;
$this->dispatcher = $dispatcher;
}
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->addCoreIndexes($output, $input->getOption('dry-run'));
+ $dryRun = $input->getOption('dry-run');
+
+ $this->addCoreIndexes($output, $dryRun);
// Dispatch event so apps can also update indexes if needed
$event = new GenericEvent($output);
$this->dispatcher->dispatch(IDBConnection::ADD_MISSING_INDEXES_EVENT, $event);
+
+ $event = new AddMissingIndicesEvent();
+ $this->eventDispatcher->dispatchTyped($event);
+
+ $missingIndices = $event->getMissingIndices();
+ if ($missingIndices !== []) {
+ $schema = new SchemaWrapper($this->connection);
+
+ foreach ($missingIndices as $missingIndex) {
+ if ($schema->hasTable($missingIndex['tableName'])) {
+ $table = $schema->getTable($missingIndex['tableName']);
+ if (!$table->hasIndex($missingIndex['indexName'])) {
+ $output->writeln('<info>Adding additional ' . $missingIndex['indexName'] . ' index to the ' . $table->getName() . ' table, this can take some time...</info>');
+ $table->addIndex($missingIndex['columns'], $missingIndex['indexName']);
+ $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
+ if ($dryRun && $sqlQueries !== null) {
+ $output->writeln($sqlQueries);
+ }
+ $output->writeln('<info>' . $table->getName() . ' table updated successfully.</info>');
+ }
+ }
+ }
+ }
+
return 0;
}
$application->add(new OC\Core\Command\Db\ConvertType(\OC::$server->getConfig(), new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig())));
$application->add(new OC\Core\Command\Db\ConvertMysqlToMB4(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection(), \OC::$server->getURLGenerator(), \OC::$server->get(LoggerInterface::class)));
$application->add(new OC\Core\Command\Db\ConvertFilecacheBigInt(\OC::$server->get(\OC\DB\Connection::class)));
- $application->add(new OC\Core\Command\Db\AddMissingIndices(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getEventDispatcher()));
+ $application->add(\OCP\Server::get(\OC\Core\Command\Db\AddMissingIndices::class));
$application->add(new OC\Core\Command\Db\AddMissingColumns(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getEventDispatcher()));
$application->add(new OC\Core\Command\Db\AddMissingPrimaryKeys(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getEventDispatcher()));
'OCP\\Contacts\\ContactsMenu\\IProvider' => $baseDir . '/lib/public/Contacts/ContactsMenu/IProvider.php',
'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => $baseDir . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php',
'OCP\\Contacts\\IManager' => $baseDir . '/lib/public/Contacts/IManager.php',
+ 'OCP\\DB\\Events\\AddMissingIndicesEvent' => $baseDir . '/lib/public/DB/Events/AddMissingIndicesEvent.php',
'OCP\\DB\\Exception' => $baseDir . '/lib/public/DB/Exception.php',
'OCP\\DB\\IPreparedStatement' => $baseDir . '/lib/public/DB/IPreparedStatement.php',
'OCP\\DB\\IResult' => $baseDir . '/lib/public/DB/IResult.php',
'OCP\\Contacts\\ContactsMenu\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IProvider.php',
'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => __DIR__ . '/../../..' . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php',
'OCP\\Contacts\\IManager' => __DIR__ . '/../../..' . '/lib/public/Contacts/IManager.php',
+ 'OCP\\DB\\Events\\AddMissingIndicesEvent' => __DIR__ . '/../../..' . '/lib/public/DB/Events/AddMissingIndicesEvent.php',
'OCP\\DB\\Exception' => __DIR__ . '/../../..' . '/lib/public/DB/Exception.php',
'OCP\\DB\\IPreparedStatement' => __DIR__ . '/../../..' . '/lib/public/DB/IPreparedStatement.php',
'OCP\\DB\\IResult' => __DIR__ . '/../../..' . '/lib/public/DB/IResult.php',
--- /dev/null
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net
+ *
+ * @author Julius Härtl <jus@bitgrid.net
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\DB\Events;
+
+/**
+ * Event to allow apps to register information about missing database indices
+ *
+ * This event will be dispatched for checking on the admin settings and when running
+ * occ db:add-missing-indices which will then create those indices
+ *
+ * @since 28.0.0
+ */
+class AddMissingIndicesEvent extends \OCP\EventDispatcher\Event {
+ /** @var array<array-key, array{tableName: string, indexName: string, columns: string[]}> */
+ private array $missingIndices = [];
+
+ /**
+ * @param string[] $columns
+ * @since 28.0.0
+ */
+ public function addMissingIndex(string $tableName, string $indexName, array $columns): void {
+ $this->missingIndices[] = [
+ 'tableName' => $tableName,
+ 'indexName' => $indexName,
+ 'columns' => $columns
+ ];
+ }
+
+ /**
+ * @since 28.0.0
+ * @return array<array-key, array{tableName: string, indexName: string, columns: string[]}>
+ */
+ public function getMissingIndices(): array {
+ return $this->missingIndices;
+ }
+}
namespace OCP;
use Doctrine\DBAL\Schema\Schema;
+use OCP\DB\Events\AddMissingIndicesEvent;
use OCP\DB\Exception;
use OCP\DB\IPreparedStatement;
use OCP\DB\IResult;
*/
interface IDBConnection {
/**
- * @deprecated 22.0.0 this is an internal event
+ * @deprecated 22.0.0 this is an internal event, use {@see AddMissingIndicesEvent} instead
*/
public const ADD_MISSING_INDEXES_EVENT = self::class . '::ADD_MISSING_INDEXES';
/**
- * @deprecated 22.0.0 this is an internal event
+ * @deprecated 22.0.0 this is an internal event, use {@see AddMissingIndicesEvent} instead
*/
public const CHECK_MISSING_INDEXES_EVENT = self::class . '::CHECK_MISSING_INDEXES';