aboutsummaryrefslogtreecommitdiffstats
path: root/core/Migrations
diff options
context:
space:
mode:
Diffstat (limited to 'core/Migrations')
-rw-r--r--core/Migrations/Version13000Date20170718121200.php1
-rw-r--r--core/Migrations/Version32000Date20250731062008.php106
2 files changed, 107 insertions, 0 deletions
diff --git a/core/Migrations/Version13000Date20170718121200.php b/core/Migrations/Version13000Date20170718121200.php
index d33d489c579..35c2d1730bc 100644
--- a/core/Migrations/Version13000Date20170718121200.php
+++ b/core/Migrations/Version13000Date20170718121200.php
@@ -658,6 +658,7 @@ class Version13000Date20170718121200 extends SimpleMigrationStep {
$table->addIndex(['uid'], 'uid_index');
$table->addIndex(['type'], 'type_index');
$table->addIndex(['category'], 'category_index');
+ $table->addUniqueIndex(['uid', 'type', 'category'], 'unique_category_per_user');
}
if (!$schema->hasTable('vcategory_to_object')) {
diff --git a/core/Migrations/Version32000Date20250731062008.php b/core/Migrations/Version32000Date20250731062008.php
new file mode 100644
index 00000000000..bf15e4a0b22
--- /dev/null
+++ b/core/Migrations/Version32000Date20250731062008.php
@@ -0,0 +1,106 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Migrations;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+use Override;
+
+/**
+ * Make sure vcategory entries are unique per user and type
+ * This migration will clean up existing duplicates
+ * and add a unique constraint to prevent future duplicates.
+ */
+class Version32000Date20250731062008 extends SimpleMigrationStep {
+ public function __construct(
+ private IDBConnection $connection,
+ ) {
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure(): ISchemaWrapper $schemaClosure
+ * @param array $options
+ */
+ #[Override]
+ public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ // Clean up duplicate categories before adding unique constraint
+ $this->cleanupDuplicateCategories($output);
+ }
+
+ /**
+ * Clean up duplicate categories
+ */
+ private function cleanupDuplicateCategories(IOutput $output) {
+ $output->info('Starting cleanup of duplicate vcategory records...');
+
+ // Find all categories, ordered to identify duplicates
+ $qb = $this->connection->getQueryBuilder();
+ $qb->select('id', 'uid', 'type', 'category')
+ ->from('vcategory')
+ ->orderBy('uid')
+ ->addOrderBy('type')
+ ->addOrderBy('category')
+ ->addOrderBy('id');
+
+ $result = $qb->executeQuery();
+
+ $seen = [];
+ $duplicateCount = 0;
+
+ while ($category = $result->fetch()) {
+ $key = $category['uid'] . '|' . $category['type'] . '|' . $category['category'];
+ $categoryId = (int)$category['id'];
+
+ if (!isset($seen[$key])) {
+ // First occurrence - keep this one
+ $seen[$key] = $categoryId;
+ continue;
+ }
+
+ // Duplicate found
+ $keepId = $seen[$key];
+ $duplicateCount++;
+
+ $output->info("Found duplicate: keeping ID $keepId, removing ID $categoryId");
+
+ // Update object references
+ $updateQb = $this->connection->getQueryBuilder();
+ $updateQb->update('vcategory_to_object')
+ ->set('categoryid', $updateQb->createNamedParameter($keepId))
+ ->where($updateQb->expr()->eq('categoryid', $updateQb->createNamedParameter($categoryId)));
+
+ $affectedRows = $updateQb->executeStatement();
+ if ($affectedRows > 0) {
+ $output->info(" - Updated $affectedRows object references from category $categoryId to $keepId");
+ }
+
+ // Remove duplicate category record
+ $deleteQb = $this->connection->getQueryBuilder();
+ $deleteQb->delete('vcategory')
+ ->where($deleteQb->expr()->eq('id', $deleteQb->createNamedParameter($categoryId)));
+
+ $deleteQb->executeStatement();
+ $output->info(" - Deleted duplicate category record ID $categoryId");
+
+ }
+
+ $result->closeCursor();
+
+ if ($duplicateCount === 0) {
+ $output->info('No duplicate categories found');
+ } else {
+ $output->info("Duplicate cleanup completed - processed $duplicateCount duplicates");
+ }
+ }
+}