From 0f03dc4c2dd558cc74ed3f4326e5fdf7978b937f Mon Sep 17 00:00:00 2001 From: Dylan Seidt Date: Sun, 29 Nov 2020 13:53:49 -0600 Subject: [PATCH] Add optimized selects Former-commit-id: ddca9a6da10318fea60b2877e409a72ebb731945 [formerly 326b6abc16d851c9b5cbd3d943ee0d2bccfd7b94] Former-commit-id: eebb37c83e3369ce0b30dc478fc8e41a53c75f1d --- src/Pagination/PaginateDirective.php | 7 ++ src/Schema/Directives/AllDirective.php | 14 ++- src/Schema/Directives/RelationDirective.php | 21 +++- src/Select/SelectDirective.php | 23 +++++ src/Select/SelectHelper.php | 109 ++++++++++++++++++++ src/lighthouse.php | 13 +++ 6 files changed, 180 insertions(+), 7 deletions(-) create mode 100644 src/Select/SelectDirective.php create mode 100644 src/Select/SelectHelper.php diff --git a/src/Pagination/PaginateDirective.php b/src/Pagination/PaginateDirective.php index 1688884057..0ad9df7093 100644 --- a/src/Pagination/PaginateDirective.php +++ b/src/Pagination/PaginateDirective.php @@ -9,6 +9,7 @@ use Nuwave\Lighthouse\Schema\AST\DocumentAST; use Nuwave\Lighthouse\Schema\Directives\BaseDirective; use Nuwave\Lighthouse\Schema\Values\FieldValue; +use Nuwave\Lighthouse\Select\SelectHelper; use Nuwave\Lighthouse\Support\Contracts\FieldManipulator; use Nuwave\Lighthouse\Support\Contracts\FieldResolver; use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; @@ -103,6 +104,12 @@ function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) $this->directiveArgValue('scopes', []) ); + if (config('lighthouse.optimized_selects')) { + $fieldSelection = array_keys($resolveInfo->getFieldSelection(2)['data']); + $selectColumns = SelectHelper::getSelectColumns($this->definitionNode, $fieldSelection, $this->getModelClass()); + $query = $query->select($selectColumns); + } + return PaginationArgs ::extractArgs($args, $this->paginationType(), $this->paginateMaxCount()) ->applyToBuilder($query); diff --git a/src/Schema/Directives/AllDirective.php b/src/Schema/Directives/AllDirective.php index c5b44b7d48..afc2a68289 100644 --- a/src/Schema/Directives/AllDirective.php +++ b/src/Schema/Directives/AllDirective.php @@ -5,6 +5,7 @@ use GraphQL\Type\Definition\ResolveInfo; use Illuminate\Database\Eloquent\Collection; use Nuwave\Lighthouse\Schema\Values\FieldValue; +use Nuwave\Lighthouse\Select\SelectHelper; use Nuwave\Lighthouse\Support\Contracts\FieldResolver; use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; @@ -35,13 +36,20 @@ public function resolveField(FieldValue $fieldValue): FieldValue { return $fieldValue->setResolver( function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Collection { - return $resolveInfo + $builder = $resolveInfo ->argumentSet ->enhanceBuilder( $this->getModelClass()::query(), $this->directiveArgValue('scopes', []) - ) - ->get(); + ); + + if (config('lighthouse.optimized_selects')) { + $fieldSelection = array_keys($resolveInfo->getFieldSelection(1)); + $selectColumns = SelectHelper::getSelectColumns($this->definitionNode, $fieldSelection, $this->getModelClass()); + $builder = $builder->select($selectColumns); + } + + return $builder->get(); } ); } diff --git a/src/Schema/Directives/RelationDirective.php b/src/Schema/Directives/RelationDirective.php index e411cb352a..044ed3e068 100644 --- a/src/Schema/Directives/RelationDirective.php +++ b/src/Schema/Directives/RelationDirective.php @@ -16,6 +16,7 @@ use Nuwave\Lighthouse\Pagination\PaginationType; use Nuwave\Lighthouse\Schema\AST\DocumentAST; use Nuwave\Lighthouse\Schema\Values\FieldValue; +use Nuwave\Lighthouse\Select\SelectHelper; use Nuwave\Lighthouse\Support\Contracts\FieldResolver; use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; @@ -27,7 +28,7 @@ public function resolveField(FieldValue $value): FieldValue function (Model $parent, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) { $relationName = $this->directiveArgValue('relation', $this->nodeName()); - $decorateBuilder = $this->makeBuilderDecorator($resolveInfo); + $decorateBuilder = $this->makeBuilderDecorator($resolveInfo, $parent, $relationName); $paginationArgs = $this->paginationArgs($args); if (config('lighthouse.batchload_relations')) { @@ -70,15 +71,27 @@ function (Model $parent, array $args, GraphQLContext $context, ResolveInfo $reso return $value; } - protected function makeBuilderDecorator(ResolveInfo $resolveInfo): Closure + protected function makeBuilderDecorator(ResolveInfo $resolveInfo, Model $parent, string $relationName): Closure { - return function ($builder) use ($resolveInfo) { - $resolveInfo + return function ($builder) use ($resolveInfo, $parent, $relationName) { + $builderDecorator = $resolveInfo ->argumentSet ->enhanceBuilder( $builder, $this->directiveArgValue('scopes', []) ); + + if (config('lighthouse.optimized_selects')) { + $fieldSelection = array_keys($resolveInfo->getFieldSelection(1)); + $selectColumns = SelectHelper::getSelectColumns($this->definitionNode, $fieldSelection, $this->getModelClass()); + $foreignKeyName = $parent->{$relationName}()->getForeignKeyName(); + + if (! in_array($foreignKeyName, $selectColumns)) { + array_push($selectColumns, $foreignKeyName); + } + + $builderDecorator->select($selectColumns); + } }; } diff --git a/src/Select/SelectDirective.php b/src/Select/SelectDirective.php new file mode 100644 index 0000000000..7bdbcc9f78 --- /dev/null +++ b/src/Select/SelectDirective.php @@ -0,0 +1,23 @@ +documentAST(); + + $type = $documentAST->types[$returnTypeName]; + + /** @var iterable<\GraphQL\Language\AST\FieldDefinitionNode> $fieldDefinitions */ + $fieldDefinitions = $type->fields; + + $model = new $modelName; + + $selectColumns = []; + + foreach ($fieldSelection as $field) { + $fieldDefinition = ASTHelper::firstByName($fieldDefinitions, $field); + + if ($fieldDefinition) { + $name = $fieldDefinition->name->value; + + if (ASTHelper::hasDirective($fieldDefinition, 'select')) { + // append selected columns in select directive to seletion + $directive = ASTHelper::directiveDefinition($fieldDefinition, 'select'); + + if ($directive) { + $selectFields = ASTHelper::directiveArgValue($directive, 'columns') ?? []; + $selectColumns = array_merge($selectColumns, $selectFields); + } + } elseif (ASTHelper::hasDirective($fieldDefinition, 'rename')) { + // append renamed attribute to selection + $directive = ASTHelper::directiveDefinition($fieldDefinition, 'rename'); + + if ($directive) { + $renamedAttribute = ASTHelper::directiveArgValue($directive, 'attribute'); + array_push($selectColumns, $renamedAttribute); + } + } elseif (ASTHelper::hasDirective($fieldDefinition, 'count')) { + // append relationship local key + $directive = ASTHelper::directiveDefinition($fieldDefinition, 'count'); + + if ($directive) { + $relationName = ASTHelper::directiveArgValue($directive, 'relation', $name); + + if ($relationName) { + array_push($selectColumns, $model->{$relationName}()->getLocalKeyName()); + } + } + } elseif (ASTHelper::hasDirective($fieldDefinition, 'hasOne')) { + // append relationship local key + $directive = ASTHelper::directiveDefinition($fieldDefinition, 'hasOne'); + + if ($directive) { + $relationName = ASTHelper::directiveArgValue($directive, 'relation', $name); + array_push($selectColumns, $model->{$relationName}()->getLocalKeyName()); + } + } elseif (ASTHelper::hasDirective($fieldDefinition, 'hasMany')) { + // append relationship local key + $directive = ASTHelper::directiveDefinition($fieldDefinition, 'hasMany'); + + if ($directive) { + $relationName = ASTHelper::directiveArgValue($directive, 'relation', $name); + array_push($selectColumns, $model->{$relationName}()->getLocalKeyName()); + } + } elseif (ASTHelper::hasDirective($fieldDefinition, 'belongsTo')) { + // append relationship foreign key + $directive = ASTHelper::directiveDefinition($fieldDefinition, 'belongsTo'); + + if ($directive) { + $relationName = ASTHelper::directiveArgValue($directive, 'relation', $name); + array_push($selectColumns, $model->{$relationName}()->getForeignKeyName()); + } + } elseif (ASTHelper::hasDirective($fieldDefinition, 'belongsToMany')) { + // append relationship foreign key + $directive = ASTHelper::directiveDefinition($fieldDefinition, 'belongsToMany'); + + if ($directive) { + $relationName = ASTHelper::directiveArgValue($directive, 'relation', $name); + array_push($selectColumns, $model->{$relationName}()->getForeignKeyName()); + } + } else { + // fallback to selecting the field name + array_push($selectColumns, $name); + } + } + } + + return array_unique($selectColumns); + } +} diff --git a/src/lighthouse.php b/src/lighthouse.php index 56cfc62c99..741ae3be84 100644 --- a/src/lighthouse.php +++ b/src/lighthouse.php @@ -295,6 +295,19 @@ 'batchload_relations' => true, + /* + |-------------------------------------------------------------------------- + | Optimized Selects + |-------------------------------------------------------------------------- + | + | If set to true, Eloquent will only select the columns neccessary to resolve + | a query. You must use the select directive to resolve advanced field dependencies + | on other columns. + | + */ + + 'optimized_selects' => true, + /* |-------------------------------------------------------------------------- | GraphQL Subscriptions