mirror of
https://github.com/ansible/awx.git
synced 2026-03-23 20:05:03 -02:30
Switch to custom ancestry table for some optimized queries
Now we can stuff some more data in this table so we can take advantage of some multi-column indexing and avoid another to join for our accessible objects and permissions queries.
This commit is contained in:
@@ -80,7 +80,12 @@ class Role(CommonModelNameNotUnique):
|
||||
role_field = models.TextField(null=False, default='')
|
||||
parents = models.ManyToManyField('Role', related_name='children')
|
||||
implicit_parents = models.TextField(null=False, default='[]')
|
||||
ancestors = models.ManyToManyField('Role', related_name='descendents') # auto-generated by `rebuild_role_ancestor_list`
|
||||
ancestors = models.ManyToManyField(
|
||||
'Role',
|
||||
through='RoleAncestorEntry',
|
||||
through_fields=('descendent', 'ancestor'),
|
||||
related_name='descendents'
|
||||
) # auto-generated by `rebuild_role_ancestor_list`
|
||||
members = models.ManyToManyField('auth.User', related_name='roles')
|
||||
content_type = models.ForeignKey(ContentType, null=True, default=None)
|
||||
object_id = models.PositiveIntegerField(null=True, default=None)
|
||||
@@ -106,9 +111,6 @@ class Role(CommonModelNameNotUnique):
|
||||
object_id=accessor.id)
|
||||
return self.ancestors.filter(pk__in=roles).exists()
|
||||
|
||||
|
||||
|
||||
|
||||
def rebuild_role_ancestor_list(self):
|
||||
'''
|
||||
Updates our `ancestors` map to accurately reflect all of the ancestors for a role
|
||||
@@ -151,25 +153,6 @@ class Role(CommonModelNameNotUnique):
|
||||
Role._simultaneous_ancestry_rebuild([self.id])
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _simultaneous_ancestry_rebuild2(role_ids_to_rebuild):
|
||||
#all_parents = Role.parents.through.values_list('to_role_id', 'from_role_id')
|
||||
|
||||
'''
|
||||
sql_params = {
|
||||
'ancestors_table': Role.ancestors.through._meta.db_table,
|
||||
'parents_table': Role.parents.through._meta.db_table,
|
||||
'roles_table': Role._meta.db_table,
|
||||
'ids': ','.join(str(x) for x in role_ids_to_rebuild)
|
||||
}
|
||||
'''
|
||||
#Expand our parent list
|
||||
#Expand our child list
|
||||
#Construct our ancestor list for
|
||||
#apply diff
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _simultaneous_ancestry_rebuild(role_ids_to_rebuild):
|
||||
#
|
||||
@@ -250,10 +233,6 @@ class Role(CommonModelNameNotUnique):
|
||||
# updated, and so we can terminate our loop.
|
||||
#
|
||||
#
|
||||
# *NOTE* Keen reader may realize that there are many instances where
|
||||
# fuck. cycles will never shake parents.
|
||||
#
|
||||
#
|
||||
|
||||
cursor = connection.cursor()
|
||||
loop_ct = 0
|
||||
@@ -274,10 +253,16 @@ class Role(CommonModelNameNotUnique):
|
||||
# TODO: Test to see if not deleting any entry that has a direct
|
||||
# correponding entry in the parents table helps reduce the processing
|
||||
# time significantly
|
||||
"""
|
||||
cursor.execute('''
|
||||
DELETE FROM %(ancestors_table)s
|
||||
WHERE to_role_id IN (%(ids)s)
|
||||
AND from_role_id != to_role_id
|
||||
WHERE ancestor_id IN (%(ids)s)
|
||||
AND descendent_id != ancestor_id
|
||||
''' % sql_params)
|
||||
"""
|
||||
cursor.execute('''
|
||||
DELETE FROM %(ancestors_table)s
|
||||
WHERE ancestor_id IN (%(ids)s)
|
||||
''' % sql_params)
|
||||
|
||||
|
||||
@@ -297,42 +282,52 @@ class Role(CommonModelNameNotUnique):
|
||||
|
||||
cursor.execute('''
|
||||
DELETE FROM %(ancestors_table)s
|
||||
WHERE from_role_id IN (%(ids)s)
|
||||
WHERE descendent_id IN (%(ids)s)
|
||||
AND
|
||||
id NOT IN (
|
||||
SELECT %(ancestors_table)s.id FROM (
|
||||
SELECT parents.from_role_id from_id, ancestors.to_role_id to_id
|
||||
SELECT parents.from_role_id from_id, ancestors.ancestor_id to_id
|
||||
FROM %(parents_table)s as parents
|
||||
LEFT JOIN %(ancestors_table)s as ancestors
|
||||
ON (parents.to_role_id = ancestors.from_role_id)
|
||||
WHERE parents.from_role_id IN (%(ids)s) AND ancestors.to_role_id IS NOT NULL
|
||||
ON (parents.to_role_id = ancestors.descendent_id)
|
||||
WHERE parents.from_role_id IN (%(ids)s) AND ancestors.ancestor_id IS NOT NULL
|
||||
|
||||
UNION
|
||||
|
||||
SELECT id from_id, id to_id from %(roles_table)s WHERE id IN (%(ids)s)
|
||||
) new_ancestry_list
|
||||
LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.from_role_id
|
||||
AND new_ancestry_list.to_id = %(ancestors_table)s.to_role_id)
|
||||
LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.descendent_id
|
||||
AND new_ancestry_list.to_id = %(ancestors_table)s.ancestor_id)
|
||||
WHERE %(ancestors_table)s.id IS NOT NULL
|
||||
)
|
||||
''' % sql_params)
|
||||
delete_ct = cursor.rowcount
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO %(ancestors_table)s (from_role_id, to_role_id)
|
||||
SELECT from_id, to_id FROM (
|
||||
SELECT parents.from_role_id from_id, ancestors.to_role_id to_id
|
||||
INSERT INTO %(ancestors_table)s (descendent_id, ancestor_id, role_field, content_type_id, object_id)
|
||||
SELECT from_id, to_id, new_ancestry_list.role_field, new_ancestry_list.content_type_id, new_ancestry_list.object_id FROM (
|
||||
SELECT parents.from_role_id from_id,
|
||||
ancestors.ancestor_id to_id,
|
||||
roles.role_field,
|
||||
COALESCE(roles.content_type_id, 0) content_type_id,
|
||||
COALESCE(roles.object_id, 0) object_id
|
||||
FROM %(parents_table)s as parents
|
||||
LEFT JOIN %(ancestors_table)s as ancestors
|
||||
ON (parents.to_role_id = ancestors.from_role_id)
|
||||
WHERE parents.from_role_id IN (%(ids)s) AND ancestors.to_role_id IS NOT NULL
|
||||
INNER JOIN %(roles_table)s as roles ON (parents.from_role_id = roles.id)
|
||||
LEFT OUTER JOIN %(ancestors_table)s as ancestors
|
||||
ON (parents.to_role_id = ancestors.descendent_id)
|
||||
WHERE parents.from_role_id IN (%(ids)s) AND ancestors.ancestor_id IS NOT NULL
|
||||
|
||||
UNION
|
||||
|
||||
SELECT id from_id, id to_id from %(roles_table)s WHERE id IN (%(ids)s)
|
||||
SELECT id from_id,
|
||||
id to_id,
|
||||
role_field,
|
||||
COALESCE(content_type_id, 0) content_type_id,
|
||||
COALESCE(object_id, 0) object_id
|
||||
from %(roles_table)s WHERE id IN (%(ids)s)
|
||||
) new_ancestry_list
|
||||
LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.from_role_id
|
||||
AND new_ancestry_list.to_id = %(ancestors_table)s.to_role_id)
|
||||
LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.descendent_id
|
||||
AND new_ancestry_list.to_id = %(ancestors_table)s.ancestor_id)
|
||||
WHERE %(ancestors_table)s.id IS NULL
|
||||
''' % sql_params)
|
||||
insert_ct = cursor.rowcount
|
||||
@@ -358,7 +353,24 @@ class Role(CommonModelNameNotUnique):
|
||||
def is_ancestor_of(self, role):
|
||||
return role.ancestors.filter(id=self.id).exists()
|
||||
|
||||
class RoleAncestorEntry(models.Model):
|
||||
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
verbose_name_plural = _('role_ancestors')
|
||||
db_table = 'main_rbac_role_ancestors'
|
||||
index_together = [
|
||||
("ancestor", "content_type_id", "role_field"),
|
||||
("descendent", "content_type_id", "role_field"),
|
||||
]
|
||||
|
||||
descendent = models.ForeignKey(Role, null=False, on_delete=models.CASCADE, related_name='+')
|
||||
ancestor = models.ForeignKey(Role, null=False, on_delete=models.CASCADE, related_name='+')
|
||||
role_field = models.TextField(null=False)
|
||||
#content_type_id = models.PositiveIntegerField(null=False)
|
||||
#object_id = models.PositiveIntegerField(null=False)
|
||||
content_type_id = models.PositiveIntegerField(null=False)
|
||||
object_id = models.PositiveIntegerField(null=False)
|
||||
|
||||
|
||||
def get_roles_on_resource(resource, accessor):
|
||||
@@ -371,15 +383,18 @@ def get_roles_on_resource(resource, accessor):
|
||||
if type(accessor) == User:
|
||||
roles = accessor.roles.all()
|
||||
elif type(accessor) == Role:
|
||||
roles = accessor
|
||||
roles = [accessor]
|
||||
else:
|
||||
accessor_type = ContentType.objects.get_for_model(accessor)
|
||||
roles = Role.objects.filter(content_type__pk=accessor_type.id,
|
||||
object_id=accessor.id)
|
||||
|
||||
return { role.role_field: True for role in
|
||||
Role.objects.filter(
|
||||
content_type = ContentType.objects.get_for_model(resource),
|
||||
object_id = resource.id,
|
||||
ancestors = roles)}
|
||||
return {
|
||||
role_field: True for role_field in
|
||||
RoleAncestorEntry.objects.filter(
|
||||
ancestor__in=roles,
|
||||
content_type_id=ContentType.objects.get_for_model(resource).id,
|
||||
object_id=resource.id
|
||||
).values_list('role_field', flat=True)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user