Skip to content

Commit

Permalink
Add optimized selects
Browse files Browse the repository at this point in the history
Former-commit-id: ddca9a6da10318fea60b2877e409a72ebb731945 [formerly 326b6ab]
Former-commit-id: eebb37c83e3369ce0b30dc478fc8e41a53c75f1d
  • Loading branch information
DDynamic committed Nov 29, 2020
1 parent e201632 commit 0f03dc4
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 7 deletions.
7 changes: 7 additions & 0 deletions src/Pagination/PaginateDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
14 changes: 11 additions & 3 deletions src/Schema/Directives/AllDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}
);
}
Expand Down
21 changes: 17 additions & 4 deletions src/Schema/Directives/RelationDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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')) {
Expand Down Expand Up @@ -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);
}
};
}

Expand Down
23 changes: 23 additions & 0 deletions src/Select/SelectDirective.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Nuwave\Lighthouse\Select;

use Nuwave\Lighthouse\Schema\Directives\BaseDirective;

class SelectDirective extends BaseDirective
{
public static function definition(): string
{
return /** @lang GraphQL */ <<<'GRAPHQL'
"""
Specify the SQL column dependencies of this field.
"""
directive @select(
"""
SQL columns names to pass to the Eloquent query builder
"""
columns: [String!]
) on FIELD_DEFINITION
GRAPHQL;
}
}
109 changes: 109 additions & 0 deletions src/Select/SelectHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Nuwave\Lighthouse\Select;

use GraphQL\Language\AST\Node;
use Illuminate\Database\Eloquent\Model;
use Nuwave\Lighthouse\Schema\AST\ASTBuilder;
use Nuwave\Lighthouse\Schema\AST\ASTHelper;

class SelectHelper
{
/**
* Given a field definition node, resolve info, and a model name, return the SQL columns that should be selected.
* Accounts for relationships and the rename and select directives.
*
* @param mixed[] $fieldSelection
* @return string[]
*/
public static function getSelectColumns(Node $definitionNode, array $fieldSelection, string $modelName): array
{
$returnTypeName = ASTHelper::getUnderlyingTypeName($definitionNode);

/** @var \Nuwave\Lighthouse\Schema\AST\DocumentAST $documentAST */
$documentAST = app(ASTBuilder::class)->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);
}
}
13 changes: 13 additions & 0 deletions src/lighthouse.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 0f03dc4

Please sign in to comment.