@@ -788,3 +788,91 @@ def test_batch_operations_preserve_correctness(repository, db):
788788 assert rvcd_qs .get (count_type = RepositoryVersionContentDetails .PRESENT ).count == 40
789789 assert rvcd_qs .filter (count_type = RepositoryVersionContentDetails .ADDED ).first () is None
790790 assert rvcd_qs .get (count_type = RepositoryVersionContentDetails .REMOVED ).count == 60
791+
792+
793+ def test_postgresql_parameter_limit (db , repository ):
794+ """
795+ Test repository operations with >65535 content units to verify PostgreSQL parameter limit
796+ workaround.
797+
798+ PostgreSQL limits queries to 65535 parameters. This test verifies that content, added(),
799+ and removed() all handle >65535 items correctly.
800+
801+ Queries MUST be evaluated via .iterator() because psycopg3 uses client-side binding for
802+ regular queries (inlining params into the SQL string, bypassing the limit) but server-side
803+ binding for server-side cursors (.iterator()), which enforces the 65,535 parameter cap.
804+ Without .iterator() the test passes even when the fix is absent.
805+ """
806+ # Create 66000 content units (exceeds PostgreSQL's 65535 parameter limit)
807+ large_content_set = [Content (pulp_type = "core.content" ) for _ in range (66000 )]
808+ Content .objects .bulk_create (large_content_set , batch_size = 2000 )
809+ large_pks = sorted ([c .pk for c in large_content_set ])
810+
811+ version0 = repository .latest_version ()
812+
813+ # Test 1: Add >65535 content units - tests added() and content with >65535 items
814+ with repository .new_version () as version1 :
815+ version1 .add_content (Content .objects .filter (pk__in = large_pks ))
816+
817+ # Verify content_ids exceeds the PostgreSQL parameter limit threshold
818+ assert isinstance (version1 .content_ids , list )
819+ assert len (version1 .content_ids ) >= 65535
820+
821+ # Test the content property with >65535 items
822+ # .iterator() forces a server-side cursor — the only way to reliably trigger the
823+ # 65,535-parameter limit in psycopg3.
824+ content_pks = set (version1 .content .values_list ("pk" , flat = True ).iterator ())
825+ assert len (content_pks ) == 66000
826+
827+ # Test the added() method with >65535 items
828+ added_pks = set (
829+ version1 .added (base_version = version0 ).values_list ("pk" , flat = True ).iterator ()
830+ )
831+ assert len (added_pks ) == 66000 # Critical: added() must handle >65535 items
832+
833+ # Test the removed() method returns nothing (nothing was removed)
834+ removed_pks = set (
835+ version1 .removed (base_version = version0 ).values_list ("pk" , flat = True ).iterator ()
836+ )
837+ assert len (removed_pks ) == 0
838+
839+ # Verify RepositoryVersionContentDetails
840+ rvcd_qs = RepositoryVersionContentDetails .objects .filter (
841+ repository_version = version1 , content_type = "core.content"
842+ )
843+ assert rvcd_qs .get (count_type = RepositoryVersionContentDetails .PRESENT ).count == 66000
844+ assert rvcd_qs .get (count_type = RepositoryVersionContentDetails .ADDED ).count == 66000
845+ assert rvcd_qs .filter (count_type = RepositoryVersionContentDetails .REMOVED ).first () is None
846+
847+ # Test 2: Remove >65535 content units - tests removed() with >65535 items
848+ with repository .new_version () as version2 :
849+ version2 .remove_content (Content .objects .filter (pk__in = large_pks ))
850+
851+ # Test the content property returns nothing (all content was removed)
852+ content_pks = set (version2 .content .values_list ("pk" , flat = True ).iterator ())
853+ assert len (content_pks ) == 0
854+
855+ # Test the added() method returns nothing (nothing was added)
856+ added_pks = set (
857+ version2 .added (base_version = version1 ).values_list ("pk" , flat = True ).iterator ()
858+ )
859+ assert len (added_pks ) == 0
860+
861+ # Test the removed() method with >65535 items
862+ removed_pks = set (
863+ version2 .removed (base_version = version1 ).values_list ("pk" , flat = True ).iterator ()
864+ )
865+ assert len (removed_pks ) == 66000 # Critical: removed() must handle >65535 items
866+
867+ # Verify RepositoryVersionContentDetails
868+ rvcd_qs = RepositoryVersionContentDetails .objects .filter (
869+ repository_version = version2 , content_type = "core.content"
870+ )
871+ assert rvcd_qs .filter (count_type = RepositoryVersionContentDetails .PRESENT ).first () is None
872+ assert rvcd_qs .filter (count_type = RepositoryVersionContentDetails .ADDED ).first () is None
873+ assert rvcd_qs .get (count_type = RepositoryVersionContentDetails .REMOVED ).count == 66000
874+
875+ # Verify we can iterate and fetch content without errors
876+ first_100 = list (version1 .content [:100 ].values_list ("pk" , flat = True ))
877+ assert len (first_100 ) == 100
878+ assert all (pk in large_pks for pk in first_100 )
0 commit comments