Skip to content

Commit

Permalink
Optimized organization node queries by replacing WHERE IN with direct…
Browse files Browse the repository at this point in the history
… JOINs

- Improved performance in organization node queries by joining tables directly instead of using WHERE IN clauses with IDs.
- Reduced the number of queries and potential overhead in fetching related organization nodes.
- This change should improve efficiency and scalability when handling larger datasets.
  • Loading branch information
yusufpangal committed Sep 11, 2024
1 parent 9a7ec7b commit 56cc029
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 6 deletions.
4 changes: 3 additions & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ parameters:
tmpDir: build/phpstan
checkOctaneCompatibility: true
checkModelProperties: true
checkMissingIterableValueType: false
ignoreErrors:
- identifier: missingType.iterableValue

# ignoreErrors:
# - '#.*Internal error.*#'
excludePaths:
Expand Down
24 changes: 24 additions & 0 deletions src/AAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use AuroraWebSoftware\AAuth\Models\RoleModelAbacRule;
use AuroraWebSoftware\AAuth\Models\User;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
Expand Down Expand Up @@ -200,6 +201,29 @@ public function organizationNodes(bool $includeRootNode = false, ?string $modelT
})->get();
}

/**
* @param bool $includeRootNode
* @param string|null $modelType
* @return OrganizationNode|Builder
* @throws Throwable
*/
public function organizationNodesQuery(bool $includeRootNode = false, ?string $modelType = null): OrganizationNode|Builder
{
$rootNodes = OrganizationNode::whereIn('id', $this->organizationNodeIds)->get();
throw_unless($rootNodes->isNotEmpty(), new InvalidOrganizationNodeException());
return OrganizationNode::where(function ($query) use ($rootNodes, $includeRootNode) {
foreach ($rootNodes as $rootNode) {
$query->orWhere('path', 'like', $rootNode->path . '/%');

if ($includeRootNode) {
$query->orWhere('path', '=', $rootNode->path);
}
}
})->when($modelType !== null, function ($query) use ($modelType) {
return $query->where('model_type', '=', $modelType);
});
}

/**
* checks if current role authorized to access given node id
*
Expand Down
2 changes: 2 additions & 0 deletions src/Facades/AAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace AuroraWebSoftware\AAuth\Facades;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Facade;

/**
Expand All @@ -23,6 +24,7 @@
*
* @method static currentRole() \AuroraWebSoftware\AAuth\Models\Role|null
* @method static ABACRules(string $modelType) array|null
* @method static organizationNodesQuery(bool $includeRootNode = false, ?string $modelType = null): OrganizationNode|Builder
*/
class AAuth extends Facade
{
Expand Down
4 changes: 3 additions & 1 deletion src/Scopes/AAuthABACModelScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ protected function applyConditionalOperator(Builder $builder, array $rule, strin

$queryMethod = $parentOperator == '||' ? 'orWhere' : 'where';

$from = sprintf('%s.', is_string($builder->getQuery()->from) ? $builder->getQuery()->from : '');

$builder->{$queryMethod}(
$rule[$operator]['attribute'],
$from.$rule[$operator]['attribute'],
$operator,
$rule[$operator]['value']
);
Expand Down
55 changes: 51 additions & 4 deletions src/Scopes/AAuthOrganizationNodeScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,60 @@
class AAuthOrganizationNodeScope implements \Illuminate\Database\Eloquent\Scope
{
/**
* @param Builder $builder
* @param Model $model
* @param Builder<Model> $builder
* @param Model $model
* @return void
*/
public function apply(Builder $builder, Model $model): void
{
$organizationNodeModelIds = AAuth::organizationNodes(true, $model::getModelType())->pluck('model_id');
$builder->whereIn('id', $organizationNodeModelIds);
$organizationNodesQuery = AAuth::organizationNodesQuery(true, $model::getModelType());
$query = $builder->getQuery();
$from = $this->getFromTableName($query->from);
$query->wheres = array_map(function ($where) use ($from) {
return $this->prefixWhereColumn($where, $from);
}, $query->wheres);

$builder->join('organization_nodes', 'organization_nodes.model_id', '=', sprintf('%s.id', $from))
->select($this->getSelectColumns($from));

$builder->mergeWheres($organizationNodesQuery->getQuery()->wheres, $organizationNodesQuery->getBindings());
}

/**
* Get the table name from the query's "from" clause
*
* @param mixed $from
* @return string
*/
protected function getFromTableName(mixed $from): string
{
return is_string($from) ? $from : '';
}

/**
* Prefix the where column with the table name if needed
*
* @param array $where
* @param string $from
* @return array
*/
protected function prefixWhereColumn(array $where, string $from): array
{
if (isset($where['column']) && ! str_contains($where['column'], $from . '.')) {
$where['column'] = sprintf('%s.%s', $from, $where['column']);
}

return $where;
}

/**
* Get the select columns for the query (only selects fields from the left table)
*
* @param string $from
* @return array
*/
protected function getSelectColumns(string $from): array
{
return ["{$from}.*"];
}
}

0 comments on commit 56cc029

Please sign in to comment.