From f1a3e13df735a074245cfd0a1c31f48a023da9e3 Mon Sep 17 00:00:00 2001 From: Dirk Julich Date: Wed, 29 Jul 2026 20:44:17 +0200 Subject: [PATCH] =?UTF-8?q?AAP-83319=20=E2=80=94=20Replace=20OR=20with=20U?= =?UTF-8?q?NION=20in=20team=20list=20RBAC=20query=20(#16554)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TeamAccess.filtered_queryset() method combined two access paths (org membership and direct read permission) using Q(…) | Q(…), which PostgreSQL could not optimize — it scanned all teams for each OR branch. Replacing the OR with UNION lets each branch use the RoleEvaluation 3-column index independently. The UNION result is wrapped in pk__in= so the outer queryset remains compatible with BaseAccess.get_queryset()'s select_related() call. Co-authored-by: Claude Opus 4.6 --- awx/main/access.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 674e04ce39..184f940614 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1232,9 +1232,11 @@ class TeamAccess(BaseAccess): Organization.access_qs(self.user, 'change').exists() or Organization.access_qs(self.user, 'audit').exists() ): return self.model.objects.all() - return self.model.objects.filter( - Q(organization__in=Organization.accessible_pk_qs(self.user, 'member_role')) | Q(pk__in=self.model.accessible_pk_qs(self.user, 'read_role')) + org_member_teams = ( + self.model.objects.filter(organization__in=Organization.accessible_pk_qs(self.user, 'member_role')).order_by().values_list('pk', flat=True) ) + direct_read_teams = self.model.objects.filter(pk__in=self.model.accessible_pk_qs(self.user, 'read_role')).order_by().values_list('pk', flat=True) + return self.model.objects.filter(pk__in=org_member_teams.union(direct_read_teams)) @check_superuser def can_add(self, data):