Skip to content

Commit 02d94c8

Browse files
committed
Permissions: Updated generation querying to be more efficient
Query of existing entity permissions during view permission generation could cause timeouts or SQL placeholder limits due to massive whereOr query generation, where an "or where" clause would be created for each entity type/id combo involved, which could be all within 20 books. This updates the query handling to use a query per type involved, with no "or where"s, and to be chunked at large entity counts. Also tweaked role-specific permission regen to chunk books at half-previous rate to prevent such a large scope being involved on each chunk. For #4695
1 parent 88ee33e commit 02d94c8

File tree

3 files changed

+50
-24
lines changed

3 files changed

+50
-24
lines changed

app/Permissions/EntityPermissionEvaluator.php

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99

1010
class EntityPermissionEvaluator
1111
{
12-
protected string $action;
13-
14-
public function __construct(string $action)
15-
{
16-
$this->action = $action;
12+
public function __construct(
13+
protected string $action
14+
) {
1715
}
1816

1917
public function evaluateEntityForUser(Entity $entity, array $userRoleIds): ?bool
@@ -82,23 +80,25 @@ protected function collapseAndCategorisePermissions(array $typeIdChain, array $p
8280
*/
8381
protected function getPermissionsMapByTypeId(array $typeIdChain, array $filterRoleIds): array
8482
{
85-
$query = EntityPermission::query()->where(function (Builder $query) use ($typeIdChain) {
86-
foreach ($typeIdChain as $typeId) {
87-
$query->orWhere(function (Builder $query) use ($typeId) {
88-
[$type, $id] = explode(':', $typeId);
89-
$query->where('entity_type', '=', $type)
90-
->where('entity_id', '=', $id);
91-
});
83+
$idsByType = [];
84+
foreach ($typeIdChain as $typeId) {
85+
[$type, $id] = explode(':', $typeId);
86+
if (!isset($idsByType[$type])) {
87+
$idsByType[$type] = [];
9288
}
93-
});
9489

95-
if (!empty($filterRoleIds)) {
96-
$query->where(function (Builder $query) use ($filterRoleIds) {
97-
$query->whereIn('role_id', [...$filterRoleIds, 0]);
98-
});
90+
$idsByType[$type][] = $id;
9991
}
10092

101-
$relevantPermissions = $query->get(['entity_id', 'entity_type', 'role_id', $this->action])->all();
93+
$relevantPermissions = [];
94+
95+
foreach ($idsByType as $type => $ids) {
96+
$idsChunked = array_chunk($ids, 10000);
97+
foreach ($idsChunked as $idChunk) {
98+
$permissions = $this->getPermissionsForEntityIdsOfType($type, $idChunk, $filterRoleIds);
99+
array_push($relevantPermissions, ...$permissions);
100+
}
101+
}
102102

103103
$map = [];
104104
foreach ($relevantPermissions as $permission) {
@@ -113,6 +113,26 @@ protected function getPermissionsMapByTypeId(array $typeIdChain, array $filterRo
113113
return $map;
114114
}
115115

116+
/**
117+
* @param string[] $ids
118+
* @param int[] $filterRoleIds
119+
* @return EntityPermission[]
120+
*/
121+
protected function getPermissionsForEntityIdsOfType(string $type, array $ids, array $filterRoleIds): array
122+
{
123+
$query = EntityPermission::query()
124+
->where('entity_type', '=', $type)
125+
->whereIn('entity_id', $ids);
126+
127+
if (!empty($filterRoleIds)) {
128+
$query->where(function (Builder $query) use ($filterRoleIds) {
129+
$query->whereIn('role_id', [...$filterRoleIds, 0]);
130+
});
131+
}
132+
133+
return $query->get(['entity_id', 'entity_type', 'role_id', $this->action])->all();
134+
}
135+
116136
/**
117137
* @return string[]
118138
*/

app/Permissions/JointPermissionBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ public function rebuildForRole(Role $role)
8383
$role->load('permissions');
8484

8585
// Chunk through all books
86-
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
86+
$this->bookFetchQuery()->chunk(10, function ($books) use ($roles) {
8787
$this->buildJointPermissionsForBooks($books, $roles);
8888
});
8989

9090
// Chunk through all bookshelves
9191
Bookshelf::query()->select(['id', 'owned_by'])
92-
->chunk(50, function ($shelves) use ($roles) {
92+
->chunk(100, function ($shelves) use ($roles) {
9393
$this->createManyJointPermissions($shelves->all(), $roles);
9494
});
9595
}

database/seeders/LargeContentSeeder.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,18 @@ public function run()
2828

2929
/** @var Book $largeBook */
3030
$largeBook = Book::factory()->create(['name' => 'Large book' . Str::random(10), 'created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
31-
$pages = Page::factory()->count(200)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
3231
$chapters = Chapter::factory()->count(50)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
33-
34-
$largeBook->pages()->saveMany($pages);
3532
$largeBook->chapters()->saveMany($chapters);
36-
$all = array_merge([$largeBook], array_values($pages->all()), array_values($chapters->all()));
33+
34+
$allPages = [];
35+
36+
foreach ($chapters as $chapter) {
37+
$pages = Page::factory()->count(100)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id, 'chapter_id' => $chapter->id]);
38+
$largeBook->pages()->saveMany($pages);
39+
array_push($allPages, ...$pages->all());
40+
}
41+
42+
$all = array_merge([$largeBook], $allPages, array_values($chapters->all()));
3743

3844
app()->make(JointPermissionBuilder::class)->rebuildForEntity($largeBook);
3945
app()->make(SearchIndex::class)->indexEntities($all);

0 commit comments

Comments
 (0)