Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions pulpcore/tests/unit/models/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -788,3 +788,87 @@ def test_batch_operations_preserve_correctness(repository, db):
assert rvcd_qs.get(count_type=RepositoryVersionContentDetails.PRESENT).count == 40
assert rvcd_qs.filter(count_type=RepositoryVersionContentDetails.ADDED).first() is None
assert rvcd_qs.get(count_type=RepositoryVersionContentDetails.REMOVED).count == 60


def test_postgresql_parameter_limit(db, repository):
"""
Test repository operations with >65535 content units to verify PostgreSQL parameter limit
workaround.

PostgreSQL limits queries to 65535 parameters. This test verifies that content, added(),
and removed() all handle >65535 items correctly.

Queries MUST be evaluated via .iterator() because psycopg3 uses client-side binding for
regular queries (inlining params into the SQL string, bypassing the limit) but server-side
binding for server-side cursors (.iterator()), which enforces the 65,535 parameter cap.
Without .iterator() the test passes even when the fix is absent.
"""
# Create 66000 content units (exceeds PostgreSQL's 65535 parameter limit)
large_content_set = [Content(pulp_type="core.content") for _ in range(66000)]
Content.objects.bulk_create(large_content_set, batch_size=2000)
large_pks = sorted([c.pk for c in large_content_set])

version0 = repository.latest_version()

# Test 1: Add >65535 content units - tests added() and content with >65535 items
with repository.new_version() as version1:
version1.add_content(Content.objects.filter(pk__in=large_pks))

# Verify content_ids exceeds the PostgreSQL parameter limit threshold
assert isinstance(version1.content_ids, list)
assert len(version1.content_ids) >= 65535

# Test the content property with >65535 items
# .iterator() forces a server-side cursor — the only way to reliably trigger the
# 65,535-parameter limit in psycopg3.
content_pks = set(version1.content.values_list("pk", flat=True).iterator())
assert len(content_pks) == 66000

# Test the added() method with >65535 items
added_pks = set(version1.added(base_version=version0).values_list("pk", flat=True).iterator())
assert len(added_pks) == 66000 # Critical: added() must handle >65535 items

# Test the removed() method returns nothing (nothing was removed)
removed_pks = set(
version1.removed(base_version=version0).values_list("pk", flat=True).iterator()
)
assert len(removed_pks) == 0

# Verify RepositoryVersionContentDetails
rvcd_qs = RepositoryVersionContentDetails.objects.filter(
repository_version=version1, content_type="core.content"
)
assert rvcd_qs.get(count_type=RepositoryVersionContentDetails.PRESENT).count == 66000
assert rvcd_qs.get(count_type=RepositoryVersionContentDetails.ADDED).count == 66000
assert rvcd_qs.filter(count_type=RepositoryVersionContentDetails.REMOVED).first() is None

# Test 2: Remove >65535 content units - tests removed() with >65535 items
with repository.new_version() as version2:
version2.remove_content(Content.objects.filter(pk__in=large_pks))

# Test the content property returns nothing (all content was removed)
content_pks = set(version2.content.values_list("pk", flat=True).iterator())
assert len(content_pks) == 0

# Test the added() method returns nothing (nothing was added)
added_pks = set(version2.added(base_version=version1).values_list("pk", flat=True).iterator())
assert len(added_pks) == 0

# Test the removed() method with >65535 items
removed_pks = set(
version2.removed(base_version=version1).values_list("pk", flat=True).iterator()
)
assert len(removed_pks) == 66000 # Critical: removed() must handle >65535 items

# Verify RepositoryVersionContentDetails
rvcd_qs = RepositoryVersionContentDetails.objects.filter(
repository_version=version2, content_type="core.content"
)
assert rvcd_qs.filter(count_type=RepositoryVersionContentDetails.PRESENT).first() is None
assert rvcd_qs.filter(count_type=RepositoryVersionContentDetails.ADDED).first() is None
assert rvcd_qs.get(count_type=RepositoryVersionContentDetails.REMOVED).count == 66000

# Verify we can iterate and fetch content without errors
first_100 = list(version1.content[:100].values_list("pk", flat=True))
assert len(first_100) == 100
assert all(pk in large_pks for pk in first_100)
Loading