From 34969c40c833b9eed98f42fc358235c56314f9cd Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Wed, 15 Apr 2026 17:31:56 +0530 Subject: [PATCH 1/2] make package_url field unique for PackageV2 Signed-off-by: Tushar Goel --- CHANGELOG.rst | 5 ++ .../migrations/0122_auto_20260415_1155.py | 36 +++++++++++ ...ns_alter_packagev2_package_url_and_more.py | 60 +++++++++++++++++++ vulnerabilities/models.py | 19 ++++++ 4 files changed, 120 insertions(+) create mode 100644 vulnerabilities/migrations/0122_auto_20260415_1155.py create mode 100644 vulnerabilities/migrations/0123_alter_packagev2_options_alter_packagev2_package_url_and_more.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 85d04ed9c..eef4ddfee 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ Release notes ============= +Version v38.5.0 +--------------------- + +- fix: Make package_url field unique for PackageV2 + Version v38.4.0 --------------------- diff --git a/vulnerabilities/migrations/0122_auto_20260415_1155.py b/vulnerabilities/migrations/0122_auto_20260415_1155.py new file mode 100644 index 000000000..0f9463302 --- /dev/null +++ b/vulnerabilities/migrations/0122_auto_20260415_1155.py @@ -0,0 +1,36 @@ +from django.db import migrations +from django.db.models import F, Window +from django.db.models.functions import RowNumber + + +def remove_duplicate_package_urls(apps, schema_editor): + PackageV2 = apps.get_model("vulnerabilities", "PackageV2") + + duplicates = ( + PackageV2.objects + .annotate( + rn=Window( + expression=RowNumber(), + partition_by=[F("package_url")], + order_by=F("id").desc(), + ) + ) + .filter(rn__gt=1) + ) + + BATCH_SIZE = 1000 + ids = list(duplicates.values_list("id", flat=True)) + + for i in range(0, len(ids), BATCH_SIZE): + PackageV2.objects.filter(id__in=ids[i:i+BATCH_SIZE]).delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ("vulnerabilities", "0121_advisoryv2_is_latest_alter_advisoryv2_advisory_id_and_more"), + ] + + operations = [ + migrations.RunPython(remove_duplicate_package_urls, migrations.RunPython.noop), + ] \ No newline at end of file diff --git a/vulnerabilities/migrations/0123_alter_packagev2_options_alter_packagev2_package_url_and_more.py b/vulnerabilities/migrations/0123_alter_packagev2_options_alter_packagev2_package_url_and_more.py new file mode 100644 index 000000000..6183a363e --- /dev/null +++ b/vulnerabilities/migrations/0123_alter_packagev2_options_alter_packagev2_package_url_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 5.2.11 on 2026-04-15 11:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("vulnerabilities", "0122_auto_20260415_1155"), + ] + + operations = [ + migrations.AlterModelOptions( + name="packagev2", + options={ + "ordering": [ + "type", + "namespace", + "name", + "version_rank", + "version", + "qualifiers", + "subpath", + ] + }, + ), + migrations.AlterField( + model_name="packagev2", + name="package_url", + field=models.CharField( + db_index=True, + help_text="The Package URL for this package.", + max_length=1000, + unique=True, + ), + ), + migrations.AlterUniqueTogether( + name="packagev2", + unique_together={("type", "namespace", "name", "version", "qualifiers", "subpath")}, + ), + migrations.AddIndex( + model_name="packagev2", + index=models.Index( + fields=["type", "namespace", "name"], name="vulnerabili_type_ca0efc_idx" + ), + ), + migrations.AddIndex( + model_name="packagev2", + index=models.Index( + fields=["type", "namespace", "name", "qualifiers", "subpath"], + name="vulnerabili_type_c98c98_idx", + ), + ), + migrations.AddIndex( + model_name="packagev2", + index=models.Index( + fields=["type", "namespace", "name", "version"], name="vulnerabili_type_1af1cc_idx" + ), + ), + ] diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index 11f4ad61e..8a91be454 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -3490,6 +3490,7 @@ class PackageV2(PackageURLMixin): null=False, help_text="The Package URL for this package.", db_index=True, + unique=True ) plain_package_url = models.CharField( @@ -3520,6 +3521,24 @@ class PackageV2(PackageURLMixin): db_index=True, ) + class Meta: + unique_together = ["type", "namespace", "name", "version", "qualifiers", "subpath"] + ordering = ["type", "namespace", "name", "version_rank", "version", "qualifiers", "subpath"] + indexes = [ + # Index for getting al versions of a package + models.Index(fields=["type", "namespace", "name"]), + models.Index(fields=["type", "namespace", "name", "qualifiers", "subpath"]), + # Index for getting a specific version of a package + models.Index( + fields=[ + "type", + "namespace", + "name", + "version", + ] + ), + ] + def __str__(self): return self.package_url From ed06995e656b26a42e4e0c6856e490e95b3fa0fe Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Wed, 15 Apr 2026 19:36:23 +0530 Subject: [PATCH 2/2] Fix errors Signed-off-by: Tushar Goel --- vulnerabilities/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index 8a91be454..c874db4e4 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -3490,7 +3490,7 @@ class PackageV2(PackageURLMixin): null=False, help_text="The Package URL for this package.", db_index=True, - unique=True + unique=True, ) plain_package_url = models.CharField(