diff --git a/.github/workflows/appstore-build-publish.yml b/.github/workflows/appstore-build-publish.yml
index 903205380d..d30033652f 100644
--- a/.github/workflows/appstore-build-publish.yml
+++ b/.github/workflows/appstore-build-publish.yml
@@ -87,7 +87,7 @@ jobs:
filename: ${{ env.APP_NAME }}/appinfo/info.xml
- name: Set up php ${{ steps.php-versions.outputs.php-min }}
- uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
+ uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
with:
php-version: ${{ steps.php-versions.outputs.php-min }}
coverage: none
diff --git a/.github/workflows/lint-php-cs.yml b/.github/workflows/lint-php-cs.yml
index 058c742290..16e72e4dd0 100644
--- a/.github/workflows/lint-php-cs.yml
+++ b/.github/workflows/lint-php-cs.yml
@@ -34,7 +34,7 @@ jobs:
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
- name: Set up php${{ steps.versions.outputs.php-min }}
- uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
+ uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
with:
php-version: ${{ steps.versions.outputs.php-min }}
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
diff --git a/.github/workflows/lint-php.yml b/.github/workflows/lint-php.yml
index 6ddc133c55..268740e2c4 100644
--- a/.github/workflows/lint-php.yml
+++ b/.github/workflows/lint-php.yml
@@ -48,7 +48,7 @@ jobs:
persist-credentials: false
- name: Set up php ${{ matrix.php-versions }}
- uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
+ uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
with:
php-version: ${{ matrix.php-versions }}
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
diff --git a/.github/workflows/phpunit-mariadb.yml b/.github/workflows/phpunit-mariadb.yml
index e04de74e88..bae575c1f0 100644
--- a/.github/workflows/phpunit-mariadb.yml
+++ b/.github/workflows/phpunit-mariadb.yml
@@ -105,7 +105,7 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
- uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
+ uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
with:
php-version: ${{ matrix.php-versions }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
diff --git a/.github/workflows/phpunit-mysql.yml b/.github/workflows/phpunit-mysql.yml
index 2c8a6538c7..a06f1b2a30 100644
--- a/.github/workflows/phpunit-mysql.yml
+++ b/.github/workflows/phpunit-mysql.yml
@@ -103,7 +103,7 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
- uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
+ uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
with:
php-version: ${{ matrix.php-versions }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
diff --git a/.github/workflows/phpunit-pgsql.yml b/.github/workflows/phpunit-pgsql.yml
index d5f491f0b9..2947edf46c 100644
--- a/.github/workflows/phpunit-pgsql.yml
+++ b/.github/workflows/phpunit-pgsql.yml
@@ -106,7 +106,7 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
- uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
+ uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
with:
php-version: ${{ matrix.php-versions }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
diff --git a/.github/workflows/phpunit-sqlite.yml b/.github/workflows/phpunit-sqlite.yml
index 2889e9ae35..6998d43ee6 100644
--- a/.github/workflows/phpunit-sqlite.yml
+++ b/.github/workflows/phpunit-sqlite.yml
@@ -95,7 +95,7 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
- uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
+ uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
with:
php-version: ${{ matrix.php-versions }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
diff --git a/.github/workflows/psalm-matrix.yml b/.github/workflows/psalm-matrix.yml
index 0bcc6ae2dc..1451544319 100644
--- a/.github/workflows/psalm-matrix.yml
+++ b/.github/workflows/psalm-matrix.yml
@@ -48,7 +48,7 @@ jobs:
persist-credentials: false
- name: Set up php${{ matrix.php-min }}
- uses: shivammathur/setup-php@ccf2c627fe61b1b4d924adfcbd19d661a18133a0 # v2.35.2
+ uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
with:
php-version: ${{ matrix.php-min }}
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
diff --git a/appinfo/info.xml b/appinfo/info.xml
index a245fa1305..60cec9b4b1 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -4,7 +4,7 @@
Polls
A polls app, similar to Doodle/DuD-Poll with the possibility to restrict access.
A polls app, similar to Doodle/DuD-Poll with the possibility to restrict access (members, certain groups/users, hidden and public).
- 8.3.0-beta.1
+ 8.3.0-beta.3
agpl
Vinzenz Rosenkranz
René Gieling
@@ -31,25 +31,11 @@
OCA\Polls\Cron\JanitorCron
OCA\Polls\Cron\AutoReminderCron
-
-
- OCA\Polls\Migration\RepairSteps\RemoveObsoleteMigrations
-
-
- OCA\Polls\Migration\RepairSteps\DropOrphanedTables
- OCA\Polls\Migration\RepairSteps\DropOrphanedColumns
- OCA\Polls\Migration\RepairSteps\DeleteInvalidRecords
- OCA\Polls\Migration\RepairSteps\UpdateHashes
- OCA\Polls\Migration\RepairSteps\CreateIndices
-
-
- OCA\Polls\Migration\RepairSteps\Install
-
-
OCA\Polls\Command\Db\CleanMigrations
OCA\Polls\Command\Db\CreateIndices
OCA\Polls\Command\Db\Purge
+ OCA\Polls\Command\Db\FixDB
OCA\Polls\Command\Db\Rebuild
OCA\Polls\Command\Db\RemoveFKConstraints
OCA\Polls\Command\Db\RemoveOptionalIndices
diff --git a/l10n/es.js b/l10n/es.js
index c49695955c..1febb29d2e 100644
--- a/l10n/es.js
+++ b/l10n/es.js
@@ -263,6 +263,7 @@ OC.L10N.register(
"minus" : "menos",
"plus" : "mas",
"Please wait…" : "Por favor, espere…",
+ "Possibly affected calendar events" : "Eventos de calendario posiblemente afectados",
"Add" : "Añadir",
"You are asked to propose more options." : "Se le pide que proponga más opciones.",
"The proposal period ends {timeRelative}." : "El período para las propuestas termina en {timeRelative}.",
diff --git a/l10n/es.json b/l10n/es.json
index ce6cf12c31..39fc1b7307 100644
--- a/l10n/es.json
+++ b/l10n/es.json
@@ -261,6 +261,7 @@
"minus" : "menos",
"plus" : "mas",
"Please wait…" : "Por favor, espere…",
+ "Possibly affected calendar events" : "Eventos de calendario posiblemente afectados",
"Add" : "Añadir",
"You are asked to propose more options." : "Se le pide que proponga más opciones.",
"The proposal period ends {timeRelative}." : "El período para las propuestas termina en {timeRelative}.",
diff --git a/lib/Command/Command.php b/lib/Command/Command.php
index 06ca6cd277..b5d8074dd2 100644
--- a/lib/Command/Command.php
+++ b/lib/Command/Command.php
@@ -24,6 +24,7 @@ class Command extends \Symfony\Component\Console\Command\Command {
protected string $description = '';
protected array $operationHints = [];
protected bool $defaultContinueAnswer = false;
+ protected bool $skipQuestion = false;
protected mixed $helper;
protected InputInterface $input;
protected OutputInterface $output;
@@ -56,6 +57,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
protected function requestConfirmation(InputInterface $input, OutputInterface $output): int {
+ if ($this->skipQuestion) {
+ return 0;
+ }
+
if ($input->isInteractive()) {
/** @var QuestionHelper */
$this->helper = $this->getHelper('question');
diff --git a/lib/Command/Db/CleanMigrations.php b/lib/Command/Db/CleanMigrations.php
index 7d7b66643e..ddbc9a7dd4 100644
--- a/lib/Command/Db/CleanMigrations.php
+++ b/lib/Command/Db/CleanMigrations.php
@@ -10,7 +10,7 @@
use Doctrine\DBAL\Schema\Schema;
use OCA\Polls\Command\Command;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
/**
diff --git a/lib/Command/Db/CreateIndices.php b/lib/Command/Db/CreateIndices.php
index cff915db88..602665ea69 100644
--- a/lib/Command/Db/CreateIndices.php
+++ b/lib/Command/Db/CreateIndices.php
@@ -10,7 +10,7 @@
use Doctrine\DBAL\Schema\Schema;
use OCA\Polls\Command\Command;
-use OCA\Polls\Db\IndexManager;
+use OCA\Polls\Db\V2\IndexManager;
use OCP\IDBConnection;
/**
diff --git a/lib/Command/Db/FixDB.php b/lib/Command/Db/FixDB.php
new file mode 100644
index 0000000000..1ef12f9add
--- /dev/null
+++ b/lib/Command/Db/FixDB.php
@@ -0,0 +1,57 @@
+schema = $this->connection->createSchema();
+ $this->tableManager->setSchema($this->schema);
+
+ $this->createOrUpdateSchema();
+
+ $this->connection->migrateToSchema($this->schema);
+
+ return 0;
+ }
+
+ /**
+ * Iterate over tables and make sure, the are created or updated
+ * according to the schema
+ */
+ private function createOrUpdateSchema(): void {
+ $this->printComment(' - Set db structure');
+ $messages = $this->tableManager->createTables();
+ $this->printInfo($messages, ' ');
+ }
+}
diff --git a/lib/Command/Db/Purge.php b/lib/Command/Db/Purge.php
index 26bf499bae..f43d1e0041 100644
--- a/lib/Command/Db/Purge.php
+++ b/lib/Command/Db/Purge.php
@@ -9,7 +9,7 @@
namespace OCA\Polls\Command\Db;
use OCA\Polls\Command\Command;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
/**
diff --git a/lib/Command/Db/Rebuild.php b/lib/Command/Db/Rebuild.php
index 623815c405..9d7f83aa3d 100644
--- a/lib/Command/Db/Rebuild.php
+++ b/lib/Command/Db/Rebuild.php
@@ -9,8 +9,8 @@
namespace OCA\Polls\Command\Db;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Db\TableManager;
-use OCA\Polls\Db\IndexManager;
+use OCA\Polls\Db\V2\TableManager;
+use OCA\Polls\Db\V2\IndexManager;
use OCA\Polls\Command\Command;
use OCP\IDBConnection;
@@ -52,32 +52,33 @@ protected function runCommands(): int {
$this->deleteGenericIndices();
$this->deleteUniqueIndices();
$this->deleteNamedIndices();
-
- $this->printComment('Step 2. Remove all orphaned tables and columns');
- $this->removeObsoleteTables();
- $this->removeObsoleteColumns();
-
$this->connection->migrateToSchema($this->schema);
+ $this->printComment('Step 2. Tidy records before rebuilding the schema');
+ $this->fixNullish();
+ $this->cleanTables();
+
$this->printComment('Step 3. Create or update tables to current shema');
$this->createOrUpdateSchema();
+ $this->connection->migrateToSchema($this->schema);
+ $this->printComment('Step 4. Remove orphaned tables and columns');
+ $this->dropObsoleteTables();
+ $this->dropObsoleteColumns();
$this->connection->migrateToSchema($this->schema);
- $this->printComment('Step 4. set hashes for votes and options');
+ $this->printComment('Step 5. Validate and fix records');
$this->migrateOptionsToHash();
-
- $this->printComment('Step 5. Remove invalid records (orphaned and duplicates)');
- $this->cleanTables();
+ $this->setLastInteraction();
$this->printComment('Step 6. Recreate unique indices and foreign key constraints');
$this->addForeignKeyConstraints();
$this->addUniqueIndices();
+ $this->connection->migrateToSchema($this->schema);
+ $this->printComment('Rebuild finished. The database structure is now up to date.');
$this->printComment('Execute \'occ db:add-missing-indices\' to add missing optional indices');
- $this->connection->migrateToSchema($this->schema);
-
return 0;
}
@@ -90,6 +91,14 @@ private function addForeignKeyConstraints(): void {
$this->printInfo($messages, ' ');
}
+ private function fixNullish(): void {
+ $this->printComment(' - Fix nullish values');
+ $messages = $this->tableManager->fixNullishShares();
+ $this->printInfo($messages, ' ');
+
+ $messages = $this->tableManager->fixNullishPollGroupRelations();
+ $this->printInfo($messages, ' ');
+ }
/**
* Create index for $table
*/
@@ -118,7 +127,7 @@ private function migrateOptionsToHash(): void {
$this->printInfo($messages, ' ');
}
- private function removeObsoleteColumns(): void {
+ private function dropObsoleteColumns(): void {
$this->printComment(' - Drop orphaned columns');
$messages = $this->tableManager->removeObsoleteColumns();
$this->printInfo($messages, ' ');
@@ -127,7 +136,7 @@ private function removeObsoleteColumns(): void {
/**
* Remove obsolete tables if they still exist
*/
- private function removeObsoleteTables(): void {
+ private function dropObsoleteTables(): void {
$this->printComment(' - Drop orphaned tables');
$messages = $this->tableManager->removeObsoleteTables();
$this->printInfo($messages, ' ');
@@ -136,8 +145,8 @@ private function removeObsoleteTables(): void {
/**
* Initialize last poll interactions timestamps
*/
- public function resetLastInteraction(): void {
- $messages = $this->tableManager->resetLastInteraction();
+ public function setLastInteraction(): void {
+ $messages = $this->tableManager->setLastInteraction();
$this->printInfo($messages, ' ');
}
@@ -146,9 +155,9 @@ public function resetLastInteraction(): void {
*/
private function cleanTables(): void {
$this->printComment(' - Remove orphaned records');
- $orphaned = $this->tableManager->removeOrphaned();
- foreach ($orphaned as $table => $count) {
- $this->printInfo(" Removed $count orphaned records from $table");
+ $messages = $this->tableManager->removeOrphaned();
+ foreach ($messages as $message) {
+ $this->printInfo(" $message");
}
$this->printComment(' - Remove duplicates');
@@ -187,7 +196,7 @@ private function deleteUniqueIndices(): void {
* remove all named indices
*/
private function deleteNamedIndices(): void {
- $this->printComment(' - Remove common indices');
+ $this->printComment(' - Remove optional indices');
$messages = $this->indexManager->removeNamedIndices();
$this->printInfo($messages, ' - ');
}
diff --git a/lib/Command/Db/RemoveFKConstraints.php b/lib/Command/Db/RemoveFKConstraints.php
index ed486c5d11..915a93eccd 100644
--- a/lib/Command/Db/RemoveFKConstraints.php
+++ b/lib/Command/Db/RemoveFKConstraints.php
@@ -10,7 +10,7 @@
use Doctrine\DBAL\Schema\Schema;
use OCA\Polls\Command\Command;
-use OCA\Polls\Db\IndexManager;
+use OCA\Polls\Db\V2\IndexManager;
use OCP\IDBConnection;
/**
diff --git a/lib/Command/Db/RemoveOptionalIndices.php b/lib/Command/Db/RemoveOptionalIndices.php
index 23fcf37a01..a90b864f45 100644
--- a/lib/Command/Db/RemoveOptionalIndices.php
+++ b/lib/Command/Db/RemoveOptionalIndices.php
@@ -10,7 +10,7 @@
use Doctrine\DBAL\Schema\Schema;
use OCA\Polls\Command\Command;
-use OCA\Polls\Db\IndexManager;
+use OCA\Polls\Db\V2\IndexManager;
use OCP\IDBConnection;
/**
diff --git a/lib/Command/Db/RemoveUniqueIndices.php b/lib/Command/Db/RemoveUniqueIndices.php
index d9ee937d6a..5d0b077489 100644
--- a/lib/Command/Db/RemoveUniqueIndices.php
+++ b/lib/Command/Db/RemoveUniqueIndices.php
@@ -10,7 +10,7 @@
use Doctrine\DBAL\Schema\Schema;
use OCA\Polls\Command\Command;
-use OCA\Polls\Db\IndexManager;
+use OCA\Polls\Db\V2\IndexManager;
use OCP\IDBConnection;
/**
diff --git a/lib/Command/Db/ResetWatch.php b/lib/Command/Db/ResetWatch.php
index 7380b36c75..aa639b2a2e 100644
--- a/lib/Command/Db/ResetWatch.php
+++ b/lib/Command/Db/ResetWatch.php
@@ -10,10 +10,10 @@
use Doctrine\DBAL\Schema\Schema;
use OCA\Polls\Command\Command;
-use OCA\Polls\Db\IndexManager;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\IndexManager;
+use OCA\Polls\Db\V2\TableManager;
use OCA\Polls\Db\Watch;
-use OCA\Polls\Migration\TableSchema;
+use OCA\Polls\Migration\V2\TableSchema;
use OCP\IDBConnection;
/**
diff --git a/lib/Cron/JanitorCron.php b/lib/Cron/JanitorCron.php
index 471ed45a82..b32b4e8e22 100644
--- a/lib/Cron/JanitorCron.php
+++ b/lib/Cron/JanitorCron.php
@@ -15,9 +15,8 @@
use OCA\Polls\Db\OptionMapper;
use OCA\Polls\Db\PollMapper;
use OCA\Polls\Db\ShareMapper;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCA\Polls\Db\VoteMapper;
-use OCA\Polls\Db\WatchMapper;
use OCA\Polls\Helper\Container;
use OCA\Polls\Model\Settings\AppSettings;
use OCP\AppFramework\Utility\ITimeFactory;
@@ -41,7 +40,6 @@ public function __construct(
private PollMapper $pollMapper,
private ShareMapper $shareMapper,
private VoteMapper $voteMapper,
- private WatchMapper $watchMapper,
private TableManager $tableManager,
) {
parent::__construct($time);
@@ -65,7 +63,7 @@ protected function run($argument) {
$this->logMapper->deleteOldEntries(time() - (86400 * 7));
// delete entries older than 1 day
- $this->watchMapper->deleteOldEntries(time() - 86400);
+ $this->tableManager->tidyWatchTable(time() - 86400);
// purge entries virtually deleted more than 12 hours ago
$deleted['comments'] = $this->commentMapper->purgeDeletedComments(time() - 4320);
@@ -93,14 +91,9 @@ protected function run($argument) {
}
// delete orphaned entries (poll_id = null)
- $orphaned = $this->tableManager->removeOrphaned();
- foreach ($orphaned as $type => $count) {
- if ($count > 0) {
- $this->logger->info(
- 'JanitorCron: Purged {count} orphaned record(s) from {type}.',
- ['count' => $count, 'type' => $type]
- );
- }
+ $messages = $this->tableManager->removeOrphaned();
+ foreach ($messages as $message) {
+ $this->logger->info('JanitorCron: ' . $message);
}
diff --git a/lib/Db/PollGroupMapper.php b/lib/Db/PollGroupMapper.php
index 2de4f2d44d..b086fa7fd4 100644
--- a/lib/Db/PollGroupMapper.php
+++ b/lib/Db/PollGroupMapper.php
@@ -9,6 +9,7 @@
namespace OCA\Polls\Db;
use Exception;
+use OCA\Polls\Helper\SqlHelper;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -144,7 +145,7 @@ protected function joinPollIds(
IQueryBuilder $qb,
string $joinAlias = 'polls',
): void {
- TableManager::getConcatenatedArray(
+ SqlHelper::getConcatenatedArray(
qb: $qb,
concatColumn: $joinAlias . '.poll_id',
asColumn: 'poll_ids',
diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php
index 78b74943da..c19c95ae3b 100644
--- a/lib/Db/PollMapper.php
+++ b/lib/Db/PollMapper.php
@@ -8,6 +8,7 @@
namespace OCA\Polls\Db;
+use OCA\Polls\Helper\SqlHelper;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IParameter;
@@ -253,7 +254,7 @@ protected function joinGroupShares(
string $joinAlias = 'group_shares',
): void {
- TableManager::getConcatenatedArray(
+ SqlHelper::getConcatenatedArray(
qb: $qb,
concatColumn: $joinAlias . '.user_id',
asColumn: 'group_shares',
@@ -285,7 +286,7 @@ protected function joinPollGroups(
string $joinAlias = 'poll_groups',
): void {
- TableManager::getConcatenatedArray(
+ SqlHelper::getConcatenatedArray(
qb: $qb,
concatColumn: $joinAlias . '.group_id',
asColumn: 'poll_groups',
@@ -325,7 +326,7 @@ protected function joinPollGroupShares(
string $joinAlias = 'poll_group_shares',
): void {
- TableManager::getConcatenatedArray(
+ SqlHelper::getConcatenatedArray(
qb: $qb,
concatColumn: $joinAlias . '.type',
asColumn: 'poll_group_user_shares',
diff --git a/lib/Db/V2/DbManager.php b/lib/Db/V2/DbManager.php
new file mode 100644
index 0000000000..938128330a
--- /dev/null
+++ b/lib/Db/V2/DbManager.php
@@ -0,0 +1,143 @@
+dbPrefix = $this->config->getSystemValue('dbtableprefix', 'oc_');
+ }
+
+ /**
+ * Set the schema.
+ * This method is used to set the schema for the database manager.
+ * It can be used to overwrite the current schema.
+ * It must be called before any other methods that require a schema.
+ * @param Schema|ISchemaWrapper $schema
+ * @return void
+ */
+ public function setSchema(Schema|ISchemaWrapper &$schema): void {
+ $this->schema = $schema;
+ }
+
+ /**
+ * Create a new schema.
+ * This method is used to create a new schema instance.
+ * It must be called before any other methods that require a schema.
+ *
+ * @throws Exception if the schema cannot be created
+ */
+ public function createSchema(): void {
+ $this->schema = $this->connection->createSchema();
+ }
+
+ /**
+ * Migrate the database to the current schema.
+ * This method is used to apply the schema changes to the database.
+ * It must be called after the schema is set.
+ *
+ * @throws InvalidClassException if the schema is not an instance of Schema class
+ */
+ public function migrateToSchema() : void {
+ // Schema must be of class Schema
+ $this->needsSchema(allowISchemWrapperClass: false);
+ $this->connection->migrateToSchema($this->schema);
+ }
+
+ /**
+ * Set the database connection.
+ * Use it to overwrite the managers own connection.
+ *
+ * @param IDBConnection $connection
+ */
+ public function setConnection(IDBConnection &$connection): void {
+ $this->connection = $connection;
+ }
+
+ /**
+ * Get the table name with the prefix.
+ * If the schema is an instance of Schema, we need to prefix the table name.
+ * ISchemaWrapper already uses the prefixed table name, but Schema does not.
+ *
+ * @param string $tableName without prefix
+ * @return string|null
+ */
+ protected function getTableName(string $tableName): ?string {
+ if ($this->schema instanceof Schema) {
+ // If the schema is an instance of Schema, we need to prefix the table name
+ return $this->dbPrefix . $tableName;
+ }
+ return $tableName;
+ }
+
+ /**
+ * Use this as a predetermined breaking point to ensure if a method needs a schema to be set.
+ *
+ * @param bool $allowSchemaClass allow schema to be an instance of Schema (default is true)
+ * @param bool $allowISchemWrapperClass allow schema to be an instance of ISchemaWrapper (default is true)
+ * @throws InvalidClassException if the schema is not set or not of a required class
+ */
+ protected function needsSchema(bool $allowSchemaClass = true, bool $allowISchemWrapperClass = true): void {
+ if (($this->schema instanceof Schema) && $allowSchemaClass) {
+ return;
+ }
+
+ if (($this->schema instanceof ISchemaWrapper) && $allowISchemWrapperClass) {
+ return;
+ }
+
+ if ($allowSchemaClass && $allowISchemWrapperClass) {
+ // If the schema is not set or not an instance of Schema or ISchemaWrapper, throw an exception
+ throw new InvalidClassException('Schema is not set or not an instance of Schema or ISchemaWrapper (caller: ' . self::formatCaller() . ')');
+ }
+ if ($allowSchemaClass) {
+ // If the schema is not set or not an instance of Schema, throw an exception
+ throw new InvalidClassException('Schema is not set or not an instance of Schema (caller: ' . self::formatCaller() . ')');
+ }
+ if ($allowISchemWrapperClass) {
+ // If the schema is not set or not an instance of ISchemaWrapper, throw an exception
+ throw new InvalidClassException('Schema is not set or not an instance of ISchemaWrapper(caller: ' . self::formatCaller() . ')');
+ }
+ throw new InvalidClassException('Unexpected. Schema is an instance of ' . get_class($this->schema) . '(caller: ' . self::formatCaller() . ')');
+ }
+
+ private static function formatCaller(int $skip = 1): string {
+ $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $skip + 2);
+ $f = $bt[$skip + 0] ?? null; // Frame of this method (0)
+ $c = $bt[$skip + 1] ?? null; // Frame of the caller (1)
+
+ $cls = $c['class'] ?? '';
+ $typ = $c['type'] ?? '';
+ $fn = $c['function'] ?? '??';
+ $fil = $c['file'] ?? ($f['file'] ?? '??');
+ $ln = $c['line'] ?? ($f['line'] ?? 0);
+
+ return sprintf('%s%s%s@%s:%d', $cls, $typ, $fn, self::short($fil), $ln);
+ }
+
+ private static function short(string $path): string {
+ $norm = str_replace('\\', '/', $path);
+ $pos = strpos($norm, '/lib/');
+ return $pos === false ? basename($norm) : substr($norm, $pos + 1); // "lib/…"
+ }
+}
diff --git a/lib/Db/IndexManager.php b/lib/Db/V2/IndexManager.php
similarity index 84%
rename from lib/Db/IndexManager.php
rename to lib/Db/V2/IndexManager.php
index ebd34a5d10..4f03a26ae2 100644
--- a/lib/Db/IndexManager.php
+++ b/lib/Db/V2/IndexManager.php
@@ -2,36 +2,31 @@
declare(strict_types=1);
/**
- * SPDX-FileCopyrightText: 2021 Nextcloud contributors
+ * SPDX-FileCopyrightText: 2025 Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-
-namespace OCA\Polls\Db;
+namespace OCA\Polls\Db\V2;
use Doctrine\DBAL\Schema\Exception\IndexDoesNotExist;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Migration\TableSchema;
+use Exception;
+use OCA\Polls\Migration\V2\TableSchema;
+use OCP\DB\ISchemaWrapper;
use OCP\IConfig;
+use OCP\IDBConnection;
-class IndexManager {
+/** @psalm-suppress UnusedClass */
+class IndexManager extends DbManager {
- private string $dbPrefix;
+ // private Schema|ISchemaWrapper $schema;
/** @psalm-suppress PossiblyUnusedMethod */
public function __construct(
- private IConfig $config,
- private Schema $schema,
+ protected IConfig $config,
+ protected IDBConnection $connection,
) {
- $this->setUp();
- }
-
- private function setUp(): void {
- $this->dbPrefix = $this->config->getSystemValue('dbtableprefix', 'oc_');
- }
-
- public function setSchema(Schema &$schema): void {
- $this->schema = $schema;
+ parent::__construct($config, $connection);
}
/**
@@ -98,8 +93,10 @@ public function createForeignKeyConstraints(): array {
* @return string log message
*/
public function createForeignKeyConstraint(string $parentTableName, string $childTableName, string $constraintColumn): string {
- $parentTableName = $this->dbPrefix . $parentTableName;
- $childTableName = $this->dbPrefix . $childTableName;
+ $this->needsSchema();
+ $parentTableName = $this->getTableName($parentTableName);
+ $childTableName = $this->getTableName($childTableName);
+
$parentTable = $this->schema->getTable($parentTableName);
$childTable = $this->schema->getTable($childTableName);
@@ -117,7 +114,8 @@ public function createForeignKeyConstraint(string $parentTableName, string $chil
* @return string log message
*/
public function createIndex(string $tableName, string $indexName, array $columns, bool $unique = false): string {
- $tableName = $this->dbPrefix . $tableName;
+ $this->needsSchema();
+ $tableName = $this->getTableName($tableName);
if ($this->schema->hasTable($tableName)) {
@@ -210,8 +208,9 @@ public function removeAllUniqueIndices(): array {
* @return string[] logged messages
*/
public function removeForeignKeysFromTable(string $tableName): array {
+ $this->needsSchema();
+ $tableName = $this->getTableName($tableName);
$messages = [];
- $tableName = $this->dbPrefix . $tableName;
if ($this->schema->hasTable($tableName)) {
@@ -233,8 +232,9 @@ public function removeForeignKeysFromTable(string $tableName): array {
* @return string[] logged messages
*/
public function removeUniqueIndicesFromTable(string $tableName): array {
+ $this->needsSchema();
+ $tableName = $this->getTableName($tableName);
$messages = [];
- $tableName = $this->dbPrefix . $tableName;
if ($this->schema->hasTable($tableName)) {
@@ -257,8 +257,9 @@ public function removeUniqueIndicesFromTable(string $tableName): array {
* @return string[] logged messages
*/
public function removeGenericIndicesFromTable(string $tableName): array {
+ $this->needsSchema();
+ $tableName = $this->getTableName($tableName);
$messages = [];
- $tableName = $this->dbPrefix . $tableName;
if ($this->schema->hasTable($tableName)) {
@@ -266,8 +267,19 @@ public function removeGenericIndicesFromTable(string $tableName): array {
foreach ($table->getIndexes() as $index) {
if (strpos($index->getName(), 'IDX_') === 0) {
- $table->dropIndex($index->getName());
- $messages[] = 'Removes ' . $index->getName() . ' from ' . $tableName;
+ try {
+ $messages[] = 'Removes ' . $index->getName() . ' from ' . $tableName;
+ $table->dropIndex($index->getName());
+ } catch (Exception $e) {
+ /**
+ * If this fails, it is not a generic index, skip it
+ *
+ * This can happen if the index is already removed
+ * For some strange reason, an index name is
+ * reported, although it does not exist anymore
+ */
+ continue;
+ }
}
}
}
@@ -282,8 +294,10 @@ public function removeGenericIndicesFromTable(string $tableName): array {
* @return null|string
*/
public function removeNamedIndexFromTable(string $tableName, string $indexName): ?string {
- $tableName = $this->dbPrefix . $tableName;
+ $this->needsSchema();
+ $tableName = $this->getTableName($tableName);
$message = null;
+
try {
if ($this->schema->hasTable($tableName)) {
$table = $this->schema->getTable($tableName);
diff --git a/lib/Db/TableManager.php b/lib/Db/V2/TableManager.php
similarity index 71%
rename from lib/Db/TableManager.php
rename to lib/Db/V2/TableManager.php
index 297ea8f461..f5ce629ec2 100644
--- a/lib/Db/TableManager.php
+++ b/lib/Db/V2/TableManager.php
@@ -2,19 +2,24 @@
declare(strict_types=1);
/**
- * SPDX-FileCopyrightText: 2021 Nextcloud contributors
+ * SPDX-FileCopyrightText: 2025 Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+namespace OCA\Polls\Db\V2;
-namespace OCA\Polls\Db;
-
-use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Type;
use Exception;
use OCA\Polls\AppConstants;
+use OCA\Polls\Db\OptionMapper;
+use OCA\Polls\Db\Poll;
+use OCA\Polls\Db\PollGroup;
+use OCA\Polls\Db\PollMapper;
+use OCA\Polls\Db\Share;
+use OCA\Polls\Db\VoteMapper;
+use OCA\Polls\Db\Watch;
use OCA\Polls\Helper\Hash;
-use OCA\Polls\Migration\TableSchema;
+use OCA\Polls\Migration\V2\TableSchema;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
@@ -22,36 +27,40 @@
use PDO;
use Psr\Log\LoggerInterface;
-class TableManager {
+class TableManager extends DbManager {
- private string $dbPrefix;
+ // private string $dbPrefix;
+ // private Schema|ISchemaWrapper $schema;
/** @psalm-suppress PossiblyUnusedMethod */
public function __construct(
- private IConfig $config,
- private IDBConnection $connection,
+ protected IConfig $config,
+ protected IDBConnection $connection,
private LoggerInterface $logger,
private OptionMapper $optionMapper,
private VoteMapper $voteMapper,
- private Schema $schema,
) {
- $this->setUp();
+ parent::__construct($config, $connection);
+ // $this->dbPrefix = $this->config->getSystemValue('dbtableprefix', 'oc_');
}
- /**
- * setUp
- */
- private function setUp(): void {
- $this->dbPrefix = $this->config->getSystemValue('dbtableprefix', 'oc_');
- }
+ // public function setSchema(Schema|ISchemaWrapper &$schema): void {
+ // $this->schema = $schema;
+ // }
- public function setSchema(Schema &$schema): void {
- $this->schema = $schema;
- }
+ // public function createSchema(): Schema {
+ // $this->schema = $this->connection->createSchema();
+ // return $this->schema;
+ // }
+ // public function migrateToSchema() : void {
+ // // Schema must be of class Schema
+ // $this->needsSchema(iSchemWrapperClass: false);
+ // $this->connection->migrateToSchema($this->schema);
+ // }
- public function setConnection(IDBConnection &$connection): void {
- $this->connection = $connection;
- }
+ // public function setConnection(IDBConnection &$connection): void {
+ // $this->connection = $connection;
+ // }
/**
* @return string[]
@@ -142,16 +151,20 @@ public function removeWatch(): array {
* @psalm-return non-empty-list
*/
public function createTable(string $tableName): array {
+ $this->needsSchema();
+
$messages = [];
$columns = TableSchema::TABLES[$tableName];
- $ocTable = $this->dbPrefix . $tableName;
- if ($this->schema->hasTable($ocTable)) {
- $table = $this->schema->getTable($ocTable);
+ // Ensure the table name is prefixed correctly
+ $tableName = $this->getTableName($tableName);
+
+ if ($this->schema->hasTable($tableName)) {
+ $table = $this->schema->getTable($tableName);
$messages[] = 'Validating table ' . $table->getName();
$tableCreated = false;
} else {
- $table = $this->schema->createTable($ocTable);
+ $table = $this->schema->createTable($tableName);
$tableCreated = true;
$messages[] = 'Creating table ' . $table->getName();
}
@@ -185,6 +198,7 @@ public function createTable(string $tableName): array {
* @psalm-return non-empty-list
*/
public function createTables(): array {
+ $this->needsSchema();
$messages = [];
foreach (array_keys(TableSchema::TABLES) as $tableName) {
@@ -319,8 +333,17 @@ public function removeOrphaned(): array {
$query->delete(Poll::TABLE)
->where($query->expr()->isNull('id'));
$orphaned[Poll::TABLE] = $query->executeStatement();
+ $messages = [];
+ foreach ($orphaned as $type => $count) {
+ if ($count > 0) {
+ $this->logger->info(
+ 'Purged ' . $count . ' orphaned record(s) from ' . $type,
+ ['count' => $count, 'type' => $type]
+ );
+ }
+ }
- return $orphaned;
+ return $messages;
}
/**
@@ -346,10 +369,30 @@ public function deleteAllDuplicates(?IOutput $output = null): array {
}
}
return $messages;
+ }
+ /**
+ * Delete entries per timestamp
+ */
+ public function tidyWatchTable(int $offset): string {
+ $query = $this->connection->getQueryBuilder();
+ $query->delete(Watch::TABLE)
+ ->where(
+ $query->expr()->lt('updated', $query->createNamedParameter($offset))
+ );
+ $count = $query->executeStatement();
+
+ if ($count > 0) {
+ $this->logger->info('Removed {number} old watch records', ['number' => $count, 'db' => $this->dbPrefix . Watch::TABLE]);
+ return 'Removed ' . $count . ' old watch records';
+ }
+
+ $this->logger->info('Watch table is clean');
+ return 'Watch table is clean';
}
private function deleteDuplicates(string $table, array $columns):int {
+ $this->needsSchema();
$qb = $this->connection->getQueryBuilder();
if ($this->schema->hasTable($this->dbPrefix . $table)) {
@@ -416,8 +459,75 @@ public function fixVotes(): void {
}
}
- public function resetLastInteraction(?int $timestamp = null): array {
+ public function fixNullishShares(): array {
$messages = [];
+ $query = $this->connection->getQueryBuilder();
+
+ // replace all nullish group_ids with 0 in share table
+ $query->update(Share::TABLE)
+ ->set('group_id', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->isNull('group_id'));
+
+ $count = $query->executeStatement();
+
+ if ($count > 0) {
+ $messages[] = 'Updated ' . $count . ' shares and set group_id to 0 for nullish values';
+ }
+
+ // replace all nullish poll_id with 0 in share table
+ $query = $this->connection->getQueryBuilder();
+ $query->update(Share::TABLE)
+ ->set('poll_id', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->isNull('poll_id'));
+
+ $count = $query->executeStatement();
+
+ if ($count > 0) {
+ $messages[] = 'Updated ' . $count . ' shares and set poll_id to 0 for nullish values';
+ }
+
+ if (empty($messages)) {
+ return ['All shares are valid'];
+ }
+
+ return $messages;
+ }
+
+ public function fixNullishPollGroupRelations(): array {
+ $messages = [];
+ $query = $this->connection->getQueryBuilder();
+
+ // replace all nullish group_ids with 0 in share table
+ $query->update(PollGroup::RELATION_TABLE)
+ ->set('group_id', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->isNull('group_id'));
+
+ $count = $query->executeStatement();
+
+ if ($count > 0) {
+ $messages[] = 'Updated ' . $count . ' pollgroup relations and set group_id to 0 for nullish values';
+ }
+
+ // replace all nullish poll_id with 0 in share table
+ $query = $this->connection->getQueryBuilder();
+ $query->update(PollGroup::RELATION_TABLE)
+ ->set('poll_id', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->isNull('poll_id'));
+
+ $count = $query->executeStatement();
+
+ if ($count > 0) {
+ $messages[] = 'Updated ' . $count . ' poll group relations and set poll_id to 0 for nullish values';
+ }
+
+ if (empty($messages)) {
+ return ['All poll group relations are valid'];
+ }
+
+ return $messages;
+ }
+
+ public function setLastInteraction(?int $timestamp = null): string {
$timestamp = $timestamp ?? time();
$query = $this->connection->getQueryBuilder();
@@ -428,16 +538,16 @@ public function resetLastInteraction(?int $timestamp = null): array {
if ($count > 0) {
$this->logger->info('Updated {number} polls in {db} and set last_interaction to current timestamp {timestamp}', ['number' => $count, 'db' => $this->dbPrefix . PollMapper::TABLE, 'timestamp' => $timestamp]);
- $messages[] = 'Updated ' . $count . ' polls';
- } else {
- $this->logger->info('No polls needed to get updated with last interaction info');
- $messages[] = 'No polls needed to get updated with last interaction info';
+ return 'Updated last interaction in ' . $count . ' polls';
}
- return $messages;
+ $this->logger->info('No polls needed to get updated with last interaction info');
+ return 'Last interaction all set';
+
}
public function migrateOptionsToHash(): array {
+ $this->needsSchema();
$messages = [];
if ($this->schema->hasTable($this->dbPrefix . OptionMapper::TABLE)) {
@@ -492,31 +602,36 @@ public function migrateOptionsToHash(): array {
return $messages;
}
- /**
- * Get a concatenated array of values from a column in the query builder.
- *
- * @param IQueryBuilder $qb The query builder instance per reference
- * @param string $concatColumn The column to concatenate
- * @param string $asColumn The alias for the concatenated column
- * @param string $dbProvider The database provider (default: IDBConnection::PLATFORM_MYSQL)
- * @param string $separator The separator for concatenation (default: ',')
- *
- * @psalm-param IDBConnection::PLATFORM_* $dbProvider
- *
- */
- public static function getConcatenatedArray(
- IQueryBuilder &$qb,
- string $concatColumn,
- string $asColumn,
- string $dbProvider,
- string $separator = ',',
- ): void {
- $qb->addSelect(match ($dbProvider) {
- IDBConnection::PLATFORM_POSTGRES => $qb->createFunction('string_agg(distinct ' . $concatColumn . '::varchar, \'' . $separator . '\') AS ' . $asColumn),
- IDBConnection::PLATFORM_ORACLE => $qb->createFunction('listagg(distinct ' . $concatColumn . ', \'' . $separator . '\') WITHIN GROUP (ORDER BY ' . $concatColumn . ') AS ' . $asColumn),
- IDBConnection::PLATFORM_SQLITE => $qb->createFunction('group_concat(replace(distinct ' . $concatColumn . ' ,\'\',\'\'), \'' . $separator . '\') AS ' . $asColumn),
- default => $qb->createFunction('group_concat(distinct ' . $concatColumn . ' SEPARATOR "' . $separator . '") AS ' . $asColumn),
- });
- }
+ // protected function getTableName(string $tableName): ?string {
+ // if ($this->schema instanceof Schema) {
+ // // If the schema is an instance of Schema, we need to prefix the table name
+ // return $this->config->getSystemValue('dbtableprefix', 'oc_') . $tableName;
+ // }
+ // return $tableName;
+ // }
+
+ // protected function needsSchema(bool $schemaClass = true, bool $iSchemWrapperClass = true): void {
+ // if (($this->schema instanceof Schema) && $schemaClass) {
+ // return;
+ // }
+
+ // if (($this->schema instanceof ISchemaWrapper) && $iSchemWrapperClass) {
+ // return;
+ // }
+
+ // if ($schemaClass && $iSchemWrapperClass) {
+ // // If the schema is not set or not an instance of Schema or ISchemaWrapper, throw an exception
+ // throw new Exception('Schema is not set or not an instance of Schema or ISchemaWrapper');
+ // }
+ // if ($schemaClass) {
+ // // If the schema is not set or not an instance of Schema, throw an exception
+ // throw new Exception('Schema is not set or not an instance of Schema');
+ // }
+ // if ($iSchemWrapperClass) {
+ // // If the schema is not set or not an instance of ISchemaWrapper, throw an exception
+ // throw new Exception('Schema is not set or not an instance of ISchemaWrapper');
+ // }
+ // throw new Exception('Unexpected. Schema is an instance of ' . get_class($this->schema));
+ // }
}
diff --git a/lib/Db/WatchMapper.php b/lib/Db/WatchMapper.php
index 48ea68b037..85ec5df2a8 100644
--- a/lib/Db/WatchMapper.php
+++ b/lib/Db/WatchMapper.php
@@ -69,17 +69,4 @@ public function findForPollIdAndTable(int $pollId, string $table): Watch {
return $this->findEntity($qb);
}
-
- /**
- * Delete entries per timestamp
- * @return void
- */
- public function deleteOldEntries(int $offset): void {
- $query = $this->db->getQueryBuilder();
- $query->delete($this->getTableName())
- ->where(
- $query->expr()->lt('updated', $query->createNamedParameter($offset))
- );
- $query->executeStatement();
- }
}
diff --git a/lib/Helper/SqlHelper.php b/lib/Helper/SqlHelper.php
new file mode 100644
index 0000000000..c40c898de0
--- /dev/null
+++ b/lib/Helper/SqlHelper.php
@@ -0,0 +1,41 @@
+addSelect(match ($dbProvider) {
+ IDBConnection::PLATFORM_POSTGRES => $qb->createFunction('string_agg(distinct ' . $concatColumn . '::varchar, \'' . $separator . '\') AS ' . $asColumn),
+ IDBConnection::PLATFORM_ORACLE => $qb->createFunction('listagg(distinct ' . $concatColumn . ', \'' . $separator . '\') WITHIN GROUP (ORDER BY ' . $concatColumn . ') AS ' . $asColumn),
+ IDBConnection::PLATFORM_SQLITE => $qb->createFunction('group_concat(replace(distinct ' . $concatColumn . ' ,\'\',\'\'), \'' . $separator . '\') AS ' . $asColumn),
+ default => $qb->createFunction('group_concat(distinct ' . $concatColumn . ' SEPARATOR "' . $separator . '") AS ' . $asColumn),
+ });
+ }
+}
diff --git a/lib/Listener/AddMissingIndicesListener.php b/lib/Listener/AddMissingIndicesListener.php
index ffc937fb12..fb2d455b0e 100644
--- a/lib/Listener/AddMissingIndicesListener.php
+++ b/lib/Listener/AddMissingIndicesListener.php
@@ -8,7 +8,7 @@
namespace OCA\Polls\Listener;
-use OCA\Polls\Migration\TableSchema;
+use OCA\Polls\Migration\V2\TableSchema;
use OCP\DB\Events\AddMissingIndicesEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
diff --git a/lib/Migration/FixVotes.php b/lib/Migration/FixVotes.php
index 90602fe1b0..6cb527f01a 100644
--- a/lib/Migration/FixVotes.php
+++ b/lib/Migration/FixVotes.php
@@ -10,7 +10,7 @@
namespace OCA\Polls\Migration;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
diff --git a/lib/Migration/RepairSteps/DeleteInvalidRecords.php b/lib/Migration/RepairSteps/CleanTables.php
similarity index 71%
rename from lib/Migration/RepairSteps/DeleteInvalidRecords.php
rename to lib/Migration/RepairSteps/CleanTables.php
index e4ecd6c2a6..1c04a4a091 100644
--- a/lib/Migration/RepairSteps/DeleteInvalidRecords.php
+++ b/lib/Migration/RepairSteps/CleanTables.php
@@ -12,8 +12,7 @@
use Doctrine\DBAL\Schema\Schema;
use Exception;
use OCA\Polls\Db\Poll;
-use OCA\Polls\Db\TableManager;
-use OCA\Polls\Db\WatchMapper;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
@@ -21,13 +20,10 @@
/**
* Preparation before migration
* Remove all invalid records to avoid erros while adding indices ans constraints
- *
- * @psalm-suppress UnusedClass
*/
-class DeleteInvalidRecords implements IRepairStep {
+class CleanTables implements IRepairStep {
public function __construct(
private IDBConnection $connection,
- private WatchMapper $watchMapper,
private TableManager $tableManager,
private Schema $schema,
) {
@@ -40,17 +36,12 @@ public function getName():string {
public function run(IOutput $output):void {
if ($this->connection->tableExists(Poll::TABLE)) {
try {
- $this->schema = $this->connection->createSchema();
- $this->tableManager->setSchema($this->schema);
-
$this->tableManager->removeOrphaned();
$this->tableManager->deleteAllDuplicates();
-
- $this->watchMapper->deleteOldEntries(time());
+ $this->tableManager->tidyWatchTable(time());
} catch (Exception $e) {
// Simply skip repair, if it breaks and rely on the next run
}
- $this->connection->migrateToSchema($this->schema);
}
}
}
diff --git a/lib/Migration/RepairSteps/CreateIndices.php b/lib/Migration/RepairSteps/CreateIndices.php
index aa6b825995..92961b4274 100644
--- a/lib/Migration/RepairSteps/CreateIndices.php
+++ b/lib/Migration/RepairSteps/CreateIndices.php
@@ -10,15 +10,12 @@
namespace OCA\Polls\Migration\RepairSteps;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Db\IndexManager;
use OCA\Polls\Db\Share;
+use OCA\Polls\Db\V2\IndexManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
-/**
- * @psalm-suppress UnusedClass
- */
class CreateIndices implements IRepairStep {
public function __construct(
private IndexManager $indexManager,
diff --git a/lib/Migration/RepairSteps/CreateTables.php b/lib/Migration/RepairSteps/CreateTables.php
index 2b5a1f2bf9..8f37c73fea 100644
--- a/lib/Migration/RepairSteps/CreateTables.php
+++ b/lib/Migration/RepairSteps/CreateTables.php
@@ -10,14 +10,11 @@
namespace OCA\Polls\Migration\RepairSteps;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
-/**
- * @psalm-suppress UnusedClass
- */
class CreateTables implements IRepairStep {
public function __construct(
private TableManager $tableManager,
diff --git a/lib/Migration/RepairSteps/DropOrphanedColumns.php b/lib/Migration/RepairSteps/DropOrphanedColumns.php
index f647aca75f..6f3aa3738b 100644
--- a/lib/Migration/RepairSteps/DropOrphanedColumns.php
+++ b/lib/Migration/RepairSteps/DropOrphanedColumns.php
@@ -10,14 +10,11 @@
namespace OCA\Polls\Migration\RepairSteps;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
-/**
- * @psalm-suppress UnusedClass
- */
class DropOrphanedColumns implements IRepairStep {
public function __construct(
private TableManager $tableManager,
diff --git a/lib/Migration/RepairSteps/DropOrphanedTables.php b/lib/Migration/RepairSteps/DropOrphanedTables.php
index 12aefdbf6e..d551549716 100644
--- a/lib/Migration/RepairSteps/DropOrphanedTables.php
+++ b/lib/Migration/RepairSteps/DropOrphanedTables.php
@@ -10,14 +10,11 @@
namespace OCA\Polls\Migration\RepairSteps;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
-/**
- * @psalm-suppress UnusedClass
- */
class DropOrphanedTables implements IRepairStep {
public function __construct(
private TableManager $tableManager,
diff --git a/lib/Migration/RepairSteps/FixNullish.php b/lib/Migration/RepairSteps/FixNullish.php
new file mode 100644
index 0000000000..a3ae393606
--- /dev/null
+++ b/lib/Migration/RepairSteps/FixNullish.php
@@ -0,0 +1,42 @@
+tableManager->setConnection($this->connection);
+
+ $messages = $this->tableManager->fixNullishShares();
+ foreach ($messages as $message) {
+ $output->info('Polls - ' . $message);
+ }
+
+ $messages = $this->tableManager->fixNullishPollGroupRelations();
+ foreach ($messages as $message) {
+ $output->info('Polls - ' . $message);
+ }
+
+ }
+}
diff --git a/lib/Migration/RepairSteps/Install.php b/lib/Migration/RepairSteps/Install.php
index ae3d9fc1e1..4ed42615ee 100644
--- a/lib/Migration/RepairSteps/Install.php
+++ b/lib/Migration/RepairSteps/Install.php
@@ -10,14 +10,11 @@
namespace OCA\Polls\Migration\RepairSteps;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Db\IndexManager;
+use OCA\Polls\Db\V2\IndexManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
-/**
- * @psalm-suppress UnusedClass
- */
class Install implements IRepairStep {
public function __construct(
private IndexManager $indexManager,
diff --git a/lib/Migration/RepairSteps/RemoveIndices.php b/lib/Migration/RepairSteps/RemoveIndices.php
index 2569e9def3..5e4d494d85 100644
--- a/lib/Migration/RepairSteps/RemoveIndices.php
+++ b/lib/Migration/RepairSteps/RemoveIndices.php
@@ -9,7 +9,7 @@
namespace OCA\Polls\Migration\RepairSteps;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Db\IndexManager;
+use OCA\Polls\Db\V2\IndexManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
@@ -19,7 +19,6 @@
* Remove all indices and foreign key constraints to avoid errors
* while changing the schema
*
- * @psalm-suppress UnusedClass
*/
class RemoveIndices implements IRepairStep {
public function __construct(
diff --git a/lib/Migration/RepairSteps/RemoveObsoleteMigrations.php b/lib/Migration/RepairSteps/RemoveObsoleteMigrations.php
index 01d9972bdb..bfacfcdf51 100644
--- a/lib/Migration/RepairSteps/RemoveObsoleteMigrations.php
+++ b/lib/Migration/RepairSteps/RemoveObsoleteMigrations.php
@@ -9,7 +9,7 @@
namespace OCA\Polls\Migration\RepairSteps;
use Doctrine\DBAL\Schema\Schema;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
@@ -19,7 +19,6 @@
* including migration versions from test releases
* theoretically, only this migration should be existent. If not, no matter
*
- * @psalm-suppress UnusedClass
*/
class RemoveObsoleteMigrations implements IRepairStep {
public function __construct(
diff --git a/lib/Migration/RepairSteps/SetLastInteraction.php b/lib/Migration/RepairSteps/SetLastInteraction.php
new file mode 100644
index 0000000000..092755ac3d
--- /dev/null
+++ b/lib/Migration/RepairSteps/SetLastInteraction.php
@@ -0,0 +1,34 @@
+tableManager->setLastInteraction();
+ $output->info($message);
+ }
+}
diff --git a/lib/Migration/RepairSteps/UpdateHashes.php b/lib/Migration/RepairSteps/UpdateHashes.php
index 7c573c5fa7..c8f3042687 100644
--- a/lib/Migration/RepairSteps/UpdateHashes.php
+++ b/lib/Migration/RepairSteps/UpdateHashes.php
@@ -9,14 +9,11 @@
namespace OCA\Polls\Migration\RepairSteps;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
-/**
- * @psalm-suppress UnusedClass
- */
class UpdateHashes implements IRepairStep {
public function __construct(
private TableManager $tableManager,
diff --git a/lib/Migration/RepairSteps/UpdateInteraction.php b/lib/Migration/RepairSteps/UpdateInteraction.php
index 312015e793..10ce2eb653 100644
--- a/lib/Migration/RepairSteps/UpdateInteraction.php
+++ b/lib/Migration/RepairSteps/UpdateInteraction.php
@@ -9,14 +9,11 @@
namespace OCA\Polls\Migration\RepairSteps;
-use OCA\Polls\Db\TableManager;
+use OCA\Polls\Db\V2\TableManager;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
-/**
- * @psalm-suppress UnusedClass
- */
class UpdateInteraction implements IRepairStep {
public function __construct(
private TableManager $tableManager,
@@ -31,9 +28,8 @@ public function getName() {
public function run(IOutput $output): void {
$this->tableManager->setConnection($this->connection);
- $messages = $this->tableManager->resetLastInteraction();
- foreach ($messages as $message) {
- $output->info($message);
- }
+ $message = $this->tableManager->setLastInteraction();
+
+ $output->info($message);
}
}
diff --git a/lib/Migration/TableSchema.php b/lib/Migration/V2/TableSchema.php
similarity index 97%
rename from lib/Migration/TableSchema.php
rename to lib/Migration/V2/TableSchema.php
index 21ead0dff9..a3ee77877a 100644
--- a/lib/Migration/TableSchema.php
+++ b/lib/Migration/V2/TableSchema.php
@@ -2,11 +2,11 @@
declare(strict_types=1);
/**
- * SPDX-FileCopyrightText: 2017 Nextcloud contributors
+ * SPDX-FileCopyrightText: 2025 Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-namespace OCA\Polls\Migration;
+namespace OCA\Polls\Migration\V2;
use OCA\Polls\Db\Comment;
use OCA\Polls\Db\Log;
@@ -208,10 +208,10 @@ abstract class TableSchema {
// 'processed', // dropped in 8.1, orphaned
],
Option::TABLE => [
- 'poll_option_hash_bin',
+ 'poll_option_hash_bin', // used and dropped in dev branch (8.3.x), leave here for security
],
Vote::TABLE => [
- 'vote_option_hash_bin',
+ 'vote_option_hash_bin', // used and dropped in dev branch (8.3.x), leave here for security
],
];
@@ -232,8 +232,8 @@ abstract class TableSchema {
],
PollGroup::RELATION_TABLE => [
'id' => ['type' => Types::BIGINT, 'options' => ['autoincrement' => true, 'notnull' => true, 'length' => 20]],
- 'poll_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => null, 'length' => 20]],
- 'group_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => null, 'length' => 20]],
+ 'poll_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]],
+ 'group_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]],
],
Poll::TABLE => [
'id' => ['type' => Types::BIGINT, 'options' => ['autoincrement' => true, 'notnull' => true, 'length' => 20]],
@@ -295,8 +295,8 @@ abstract class TableSchema {
],
Share::TABLE => [
'id' => ['type' => Types::BIGINT, 'options' => ['autoincrement' => true, 'notnull' => true, 'length' => 20]],
- 'poll_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => null, 'length' => 20]],
- 'group_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => null, 'length' => 20]],
+ 'poll_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]],
+ 'group_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]],
'token' => ['type' => Types::STRING, 'options' => ['notnull' => true, 'default' => '', 'length' => 64]],
'type' => ['type' => Types::STRING, 'options' => ['notnull' => true, 'default' => '', 'length' => 64]],
'label' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 256]],
diff --git a/lib/Migration/Version060100Date20240209073304.php b/lib/Migration/Version060100Date20240209073304.php
deleted file mode 100644
index 17e7cde380..0000000000
--- a/lib/Migration/Version060100Date20240209073304.php
+++ /dev/null
@@ -1,117 +0,0 @@
-schema = $schemaClosure();
- $messages = $this->createTables();
-
- foreach ($messages as $message) {
- $output->info('Polls - ' . $message);
- };
-
- return $this->schema;
- }
-
- /**
- * @return void
- */
- public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
- $now = time();
- $query = $this->connection->getQueryBuilder();
- $query->update(Poll::TABLE)
- ->set('last_interaction', $query->createNamedParameter($now))
- ->where($query->expr()->eq('last_interaction', $query->createNamedParameter(0)));
- $query->executeStatement();
- }
-
-
- /**
- * @return string[]
- *
- * @psalm-return non-empty-list
- */
- public function createTable(string $tableName, array $columns): array {
- $messages = [];
-
- if ($this->schema->hasTable($tableName)) {
- $table = $this->schema->getTable($tableName);
- $messages[] = 'Validating table ' . $table->getName();
- $tableCreated = false;
- } else {
- $table = $this->schema->createTable($tableName);
- $tableCreated = true;
- $messages[] = 'Creating table ' . $table->getName();
- }
-
- foreach ($columns as $columnName => $columnDefinition) {
- if ($table->hasColumn($columnName)) {
- $column = $table->getColumn($columnName);
- if (Type::lookupName($column->getType()) !== $columnDefinition['type']) {
- $messages[] = 'Migrated type of ' . $table->getName() . '[\'' . $columnName . '\'] from ' . Type::lookupName($column->getType()) . ' to ' . $columnDefinition['type'];
- $column->setType(Type::getType($columnDefinition['type']));
- }
- $column->setOptions($columnDefinition['options']);
-
- // force change to current options definition
- $table->modifyColumn($columnName, $columnDefinition['options']);
- } else {
- $table->addColumn($columnName, $columnDefinition['type'], $columnDefinition['options']);
- $messages[] = 'Added ' . $table->getName() . ', ' . $columnName . ' (' . $columnDefinition['type'] . ')';
- }
- }
-
- if ($tableCreated) {
- $table->setPrimaryKey(['id']);
- }
- return $messages;
- }
-
- /**
- * @return string[]
- *
- * @psalm-return non-empty-list
- */
- public function createTables(): array {
- $messages = [];
-
- foreach (TableSchema::TABLES as $tableName => $columns) {
- $messages = array_merge($messages, $this->createTable($tableName, $columns));
- }
- return $messages;
- }
-}
diff --git a/lib/Migration/Version080000Date20250622002601.php b/lib/Migration/Version080000Date20250622002601.php
deleted file mode 100644
index 44e887f083..0000000000
--- a/lib/Migration/Version080000Date20250622002601.php
+++ /dev/null
@@ -1,103 +0,0 @@
-schema = $schemaClosure();
- $messages = $this->createTables();
-
- foreach ($messages as $message) {
- $output->info('Polls - ' . $message);
- };
-
- return $this->schema;
- }
-
- /**
- * @return string[]
- *
- * @psalm-return non-empty-list
- */
- public function createTable(string $tableName, array $columns): array {
- $messages = [];
-
- if ($this->schema->hasTable($tableName)) {
- $table = $this->schema->getTable($tableName);
- $messages[] = 'Validating table ' . $table->getName();
- $tableCreated = false;
- } else {
- $table = $this->schema->createTable($tableName);
- $tableCreated = true;
- $messages[] = 'Creating table ' . $table->getName();
- }
-
- foreach ($columns as $columnName => $columnDefinition) {
- if ($table->hasColumn($columnName)) {
- $column = $table->getColumn($columnName);
- if (Type::lookupName($column->getType()) !== $columnDefinition['type']) {
- $messages[] = 'Migrated type of ' . $table->getName() . '[\'' . $columnName . '\'] from ' . Type::lookupName($column->getType()) . ' to ' . $columnDefinition['type'];
- $column->setType(Type::getType($columnDefinition['type']));
- }
- $column->setOptions($columnDefinition['options']);
-
- // force change to current options definition
- $table->modifyColumn($columnName, $columnDefinition['options']);
- } else {
- $table->addColumn($columnName, $columnDefinition['type'], $columnDefinition['options']);
- $messages[] = 'Added ' . $table->getName() . ', ' . $columnName . ' (' . $columnDefinition['type'] . ')';
- }
- }
-
- if ($tableCreated) {
- $table->setPrimaryKey(['id']);
- }
- return $messages;
- }
-
- /**
- * @return string[]
- *
- * @psalm-return non-empty-list
- */
- public function createTables(): array {
- $messages = [];
-
- foreach (TableSchema::TABLES as $tableName => $columns) {
- $messages = array_merge($messages, $this->createTable($tableName, $columns));
- }
- return $messages;
- }
-}
diff --git a/lib/Migration/Version080100Date20250623202002.php b/lib/Migration/Version080100Date20250623202002.php
deleted file mode 100644
index fc49e31c09..0000000000
--- a/lib/Migration/Version080100Date20250623202002.php
+++ /dev/null
@@ -1,103 +0,0 @@
-schema = $schemaClosure();
- $messages = $this->createTables();
-
- foreach ($messages as $message) {
- $output->info('Polls - ' . $message);
- };
-
- return $this->schema;
- }
-
- /**
- * @return string[]
- *
- * @psalm-return non-empty-list
- */
- public function createTable(string $tableName, array $columns): array {
- $messages = [];
-
- if ($this->schema->hasTable($tableName)) {
- $table = $this->schema->getTable($tableName);
- $messages[] = 'Validating table ' . $table->getName();
- $tableCreated = false;
- } else {
- $table = $this->schema->createTable($tableName);
- $tableCreated = true;
- $messages[] = 'Creating table ' . $table->getName();
- }
-
- foreach ($columns as $columnName => $columnDefinition) {
- if ($table->hasColumn($columnName)) {
- $column = $table->getColumn($columnName);
- if (Type::lookupName($column->getType()) !== $columnDefinition['type']) {
- $messages[] = 'Migrated type of ' . $table->getName() . '[\'' . $columnName . '\'] from ' . Type::lookupName($column->getType()) . ' to ' . $columnDefinition['type'];
- $column->setType(Type::getType($columnDefinition['type']));
- }
- $column->setOptions($columnDefinition['options']);
-
- // force change to current options definition
- $table->modifyColumn($columnName, $columnDefinition['options']);
- } else {
- $table->addColumn($columnName, $columnDefinition['type'], $columnDefinition['options']);
- $messages[] = 'Added ' . $table->getName() . ', ' . $columnName . ' (' . $columnDefinition['type'] . ')';
- }
- }
-
- if ($tableCreated) {
- $table->setPrimaryKey(['id']);
- }
- return $messages;
- }
-
- /**
- * @return string[]
- *
- * @psalm-return non-empty-list
- */
- public function createTables(): array {
- $messages = [];
-
- foreach (TableSchema::TABLES as $tableName => $columns) {
- $messages = array_merge($messages, $this->createTable($tableName, $columns));
- }
- return $messages;
- }
-}
diff --git a/lib/Migration/Version080300Date20250812231603.php b/lib/Migration/Version080300Date20250812231603.php
deleted file mode 100644
index 561adb9ee7..0000000000
--- a/lib/Migration/Version080300Date20250812231603.php
+++ /dev/null
@@ -1,103 +0,0 @@
-schema = $schemaClosure();
- $messages = $this->createTables();
-
- foreach ($messages as $message) {
- $output->info('Polls - ' . $message);
- };
-
- return $this->schema;
- }
-
- /**
- * @return string[]
- *
- * @psalm-return non-empty-list
- */
- public function createTable(string $tableName, array $columns): array {
- $messages = [];
-
- if ($this->schema->hasTable($tableName)) {
- $table = $this->schema->getTable($tableName);
- $messages[] = 'Validating table ' . $table->getName();
- $tableCreated = false;
- } else {
- $table = $this->schema->createTable($tableName);
- $tableCreated = true;
- $messages[] = 'Creating table ' . $table->getName();
- }
-
- foreach ($columns as $columnName => $columnDefinition) {
- if ($table->hasColumn($columnName)) {
- $column = $table->getColumn($columnName);
- if (Type::lookupName($column->getType()) !== $columnDefinition['type']) {
- $messages[] = 'Migrated type of ' . $table->getName() . '[\'' . $columnName . '\'] from ' . Type::lookupName($column->getType()) . ' to ' . $columnDefinition['type'];
- $column->setType(Type::getType($columnDefinition['type']));
- }
- $column->setOptions($columnDefinition['options']);
-
- // force change to current options definition
- $table->modifyColumn($columnName, $columnDefinition['options']);
- } else {
- $table->addColumn($columnName, $columnDefinition['type'], $columnDefinition['options']);
- $messages[] = 'Added ' . $table->getName() . ', ' . $columnName . ' (' . $columnDefinition['type'] . ')';
- }
- }
-
- if ($tableCreated) {
- $table->setPrimaryKey(['id']);
- }
- return $messages;
- }
-
- /**
- * @return string[]
- *
- * @psalm-return non-empty-list
- */
- public function createTables(): array {
- $messages = [];
-
- foreach (TableSchema::TABLES as $tableName => $columns) {
- $messages = array_merge($messages, $this->createTable($tableName, $columns));
- }
- return $messages;
- }
-}
diff --git a/lib/Migration/Version080300Date20250816201201.php b/lib/Migration/Version080300Date20250816201201.php
new file mode 100644
index 0000000000..602f1db2f6
--- /dev/null
+++ b/lib/Migration/Version080300Date20250816201201.php
@@ -0,0 +1,164 @@
+output) {
+ if (is_array($message)) {
+ foreach ($message as $msg) {
+ $this->output->info($prefix . 'Polls - ' . $msg);
+ }
+ } else {
+ $this->output->info($prefix . 'Polls - ' . $message);
+ }
+ }
+ }
+
+ /**
+ * This method is called before the schema change.
+ * All the existing calls are necessary to prepare the database for the migration.
+ * Main steps:
+ * 1. Make sure that no nullish values are used for poll_id and group_id in the share table
+ * 2. Remove all orphaned records which have no relation to a poll group (shares) or a poll (all)
+ * 3. Remove all duplicate records based on unique index definition
+ * 4. Tidy the watch table by removing all entries which are older than now
+ *
+ * @param IOutput $output
+ * @param \Closure $schemaClosure
+ * @param array $options
+ * @return void
+ */
+ public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void {
+ $this->output = $output;
+ $this->logInfo('Prepare migration');
+
+ // remove foreign keys and unique indices from the share table in preparation of fixing nullish values
+ $this->indexManager->createSchema(); // Let the indexManager use it's own schema
+ $message = $this->indexManager->removeUniqueIndicesFromTable(Share::TABLE);
+ $this->logInfo($message, 'preMigration: ');
+ $message = $this->indexManager->removeForeignKeysFromTable(Share::TABLE);
+ $this->logInfo($message, 'preMigration: ');
+ $this->indexManager->migrateToSchema();
+
+ // fix nullish values in poll_id and group_id and set 0 in case of null
+ $message = $this->tableManager->fixNullishShares();
+ $this->logInfo($message, 'preMigration: ');
+
+ // remove all orphaned records
+ $message = $this->tableManager->removeOrphaned();
+ $this->logInfo($message, 'preMigration: ');
+
+ // remove all duplicates
+ $this->tableManager->createSchema();
+ $message = $this->tableManager->deleteAllDuplicates();
+ $this->logInfo($message, 'preMigration: ');
+ $this->tableManager->migrateToSchema();
+
+ $message = $this->tableManager->tidyWatchTable(time());
+ $this->logInfo($message, 'preMigration: ');
+ }
+
+ /**
+ * This method is executing the actual schema change based on the definition of TableSchema
+ * $schemaClosure The `\Closure` returns an `ISchemaWrapper`
+ * @param IOutput $output
+ * @param \Closure $schemaClosure
+ * @param array $options
+ * @return ISchemaWrapper|null
+ */
+ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ $this->output = $output;
+ $this->schema = $schemaClosure();
+ $this->tableManager->setConnection($this->connection);
+ $this->tableManager->setSchema($this->schema);
+
+ $message = $this->tableManager->createTables();
+ $this->logInfo($message, 'runMigration: ');
+
+ if (!($this->schema instanceof ISchemaWrapper)) {
+ return null;
+ }
+
+ return $this->schema;
+ }
+
+ /**
+ * This method is called after the schema change.
+ * It is used to perform any post-migration steps, such as migrating options to a hash.
+ * Main steps:
+ * 1. Ensure that option hashes are created correctly for options and votes
+ *
+ * @param IOutput $output
+ * @param \Closure $schemaClosure
+ * @param array $options
+ * @return void
+ */
+ public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void {
+ $this->output = $output;
+ $this->logInfo('Post migration steps');
+
+ $this->tableManager->createSchema();
+ $message = $this->tableManager->migrateOptionsToHash();
+ $this->logInfo($message, 'postMigration: ');
+
+ $message = $this->tableManager->removeObsoleteTables();
+ $this->logInfo($message, 'postMigration: ');
+
+ $this->tableManager->createSchema();
+ $message = $this->tableManager->removeObsoleteColumns();
+ $this->tableManager->migrateToSchema();
+ $this->logInfo($message, 'postMigration: ');
+
+
+ $this->indexManager->createSchema();
+
+ $message = $this->indexManager->createForeignKeyConstraints();
+ $this->logInfo($message, 'postMigration: ');
+
+ $message = $this->indexManager->createUniqueIndices();
+ $this->logInfo($message, 'postMigration: ');
+
+ // skip creating optional indices and leave it to 'occ db:add-missing-indices'
+ $this->indexManager->migrateToSchema();
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index e4b19e66ed..a57c9d6d1a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "polls",
- "version": "8.3.0-beta.1",
+ "version": "8.3.0-beta.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "polls",
- "version": "8.3.0-beta.1",
+ "version": "8.3.0-beta.3",
"license": "AGPL-3.0",
"dependencies": {
"@nextcloud/auth": "^2.5.1",
diff --git a/package.json b/package.json
index f1a1d4eda4..bf47a6f495 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "polls",
- "version": "8.3.0-beta.1",
+ "version": "8.3.0-beta.3",
"private": true,
"description": "Polls app for nextcloud",
"homepage": "https://github.com/nextcloud/polls#readme",
diff --git a/psalm.xml b/psalm.xml
index 1fa846a343..c55cd6e3ff 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -28,6 +28,13 @@
+
+
+
+
+
+
+