mirror of
https://github.com/nextcloud/server.git
synced 2026-02-20 00:12:30 -05:00
Merge pull request #54165 from nextcloud/fix/unique-vcategory
fix(core): ensure unique vcategory
This commit is contained in:
commit
687df7c8ab
7 changed files with 116 additions and 2 deletions
|
|
@ -210,5 +210,11 @@ class AddMissingIndicesListener implements IEventListener {
|
|||
'systag_objecttype',
|
||||
['objecttype']
|
||||
);
|
||||
|
||||
$event->addMissingUniqueIndex(
|
||||
'vcategory',
|
||||
'unique_category_per_user',
|
||||
['uid', 'type', 'category']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')) {
|
||||
|
|
|
|||
106
core/Migrations/Version32000Date20250731062008.php
Normal file
106
core/Migrations/Version32000Date20250731062008.php
Normal file
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1510,6 +1510,7 @@ return array(
|
|||
'OC\\Core\\Migrations\\Version31000Date20240814184402' => $baseDir . '/core/Migrations/Version31000Date20240814184402.php',
|
||||
'OC\\Core\\Migrations\\Version31000Date20250213102442' => $baseDir . '/core/Migrations/Version31000Date20250213102442.php',
|
||||
'OC\\Core\\Migrations\\Version32000Date20250620081925' => $baseDir . '/core/Migrations/Version32000Date20250620081925.php',
|
||||
'OC\\Core\\Migrations\\Version32000Date20250731062008' => $baseDir . '/core/Migrations/Version32000Date20250731062008.php',
|
||||
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
|
||||
'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php',
|
||||
'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
|
||||
|
|
|
|||
|
|
@ -1551,6 +1551,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\Core\\Migrations\\Version31000Date20240814184402' => __DIR__ . '/../../..' . '/core/Migrations/Version31000Date20240814184402.php',
|
||||
'OC\\Core\\Migrations\\Version31000Date20250213102442' => __DIR__ . '/../../..' . '/core/Migrations/Version31000Date20250213102442.php',
|
||||
'OC\\Core\\Migrations\\Version32000Date20250620081925' => __DIR__ . '/../../..' . '/core/Migrations/Version32000Date20250620081925.php',
|
||||
'OC\\Core\\Migrations\\Version32000Date20250731062008' => __DIR__ . '/../../..' . '/core/Migrations/Version32000Date20250731062008.php',
|
||||
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
|
||||
'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php',
|
||||
'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
|
||||
|
|
|
|||
|
|
@ -273,7 +273,6 @@ class Tags implements ITags {
|
|||
return false;
|
||||
}
|
||||
if ($this->userHasTag($name, $this->user)) {
|
||||
// TODO use unique db properties instead of an additional check
|
||||
$this->logger->debug(__METHOD__ . ' Tag with name already exists', ['app' => 'core']);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patch level
|
||||
// when updating major/minor version number.
|
||||
|
||||
$OC_Version = [32, 0, 0, 1];
|
||||
$OC_Version = [32, 0, 0, 2];
|
||||
|
||||
// The human-readable string
|
||||
$OC_VersionString = '32.0.0 dev';
|
||||
|
|
|
|||
Loading…
Reference in a new issue