mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-10-13 16:59:42 +00:00
Enforce filename uniqueness in StoredObjectVersion
with partial unique index...
This commit is contained in:
6
.changes/unreleased/Fixed-20251013-123932.yaml
Normal file
6
.changes/unreleased/Fixed-20251013-123932.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Fixed
|
||||
body: Add unique condition on stored object filename, with cleaning step on existing duplicate filenames
|
||||
time: 2025-10-13T12:39:32.85885314+02:00
|
||||
custom:
|
||||
Issue: "446"
|
||||
SchemaChange: Drop or rename table or columns, or enforce new constraint that must be manually fixed
|
@@ -23,10 +23,14 @@ use Random\RandomException;
|
||||
* Store each version of StoredObject's.
|
||||
*
|
||||
* A version should not be created manually: use the method @see{StoredObject::registerVersion} instead.
|
||||
*
|
||||
* Each filename must be unique within the same StoredObject. We add a condition on id to apply this condition only for
|
||||
* newly created versions when this new index is applied.
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('chill_doc.stored_object_version')]
|
||||
#[ORM\UniqueConstraint(name: 'chill_doc_stored_object_version_unique_by_object', columns: ['stored_object_id', 'version'])]
|
||||
#[ORM\UniqueConstraint(name: 'chill_doc_stored_object_version_unique_by_filename', columns: ['filename'], options: ['where' => '(id > 0)'])]
|
||||
class StoredObjectVersion implements TrackCreationInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
|
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\DocStore;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251013094414 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'DocStore: Enforce filename uniqueness on chill_doc.stored_object_version; clean duplicates and add partial unique index on filename (for new rows only).';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// 1) Clean duplicates: for each (stored_object_id, filename, key, iv), keep only the last inserted row
|
||||
// and delete all others. Use ROW_NUMBER over id DESC to define the last one.
|
||||
$this->addSql(<<<'SQL'
|
||||
WITH ranked AS (
|
||||
SELECT id,
|
||||
rank() OVER (
|
||||
PARTITION BY stored_object_id, filename, "key"::jsonb, iv::jsonb
|
||||
ORDER BY id DESC
|
||||
) AS rn
|
||||
FROM chill_doc.stored_object_version
|
||||
)
|
||||
DELETE FROM chill_doc.stored_object_version sov
|
||||
USING ranked r
|
||||
WHERE sov.id = r.id
|
||||
AND r.rn > 1
|
||||
SQL);
|
||||
|
||||
// 2) Create a partial unique index on filename that applies only to subsequently inserted rows.
|
||||
// Per user's instruction, compute the cutoff using the stored_object_id sequence value.
|
||||
$nextVal = (int) $this->connection->fetchOne("SELECT nextval('chill_doc.stored_object_version_id_seq')");
|
||||
|
||||
// Safety: if somehow sequence is not available, fallback to current max id from the table
|
||||
if ($nextVal <= 0) {
|
||||
$nextVal = (int) $this->connection->fetchOne('SELECT COALESCE(MAX(id), 0) FROM chill_doc.stored_object_version');
|
||||
}
|
||||
|
||||
$this->addSql(sprintf(
|
||||
'CREATE UNIQUE INDEX chill_doc_stored_object_version_unique_by_filename ON chill_doc.stored_object_version (filename) WHERE id > %d',
|
||||
$nextVal
|
||||
));
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// Drop the partial unique index; data cleanup is irreversible.
|
||||
$this->addSql('DROP INDEX IF EXISTS chill_doc_stored_object_version_unique_by_filename');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user