Skip to content

Commit 326b6ab

Browse files
committed
Add optimized selects
1 parent 3a3e686 commit 326b6ab

File tree

6 files changed

+180
-7
lines changed

6 files changed

+180
-7
lines changed

src/Pagination/PaginateDirective.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Nuwave\Lighthouse\Schema\AST\DocumentAST;
1010
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
1111
use Nuwave\Lighthouse\Schema\Values\FieldValue;
12+
use Nuwave\Lighthouse\Select\SelectHelper;
1213
use Nuwave\Lighthouse\Support\Contracts\FieldManipulator;
1314
use Nuwave\Lighthouse\Support\Contracts\FieldResolver;
1415
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
@@ -103,6 +104,12 @@ function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo)
103104
$this->directiveArgValue('scopes', [])
104105
);
105106

107+
if (config('lighthouse.optimized_selects')) {
108+
$fieldSelection = array_keys($resolveInfo->getFieldSelection(2)['data']);
109+
$selectColumns = SelectHelper::getSelectColumns($this->definitionNode, $fieldSelection, $this->getModelClass());
110+
$query = $query->select($selectColumns);
111+
}
112+
106113
return PaginationArgs
107114
::extractArgs($args, $this->paginationType(), $this->paginateMaxCount())
108115
->applyToBuilder($query);

src/Schema/Directives/AllDirective.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use GraphQL\Type\Definition\ResolveInfo;
66
use Illuminate\Database\Eloquent\Collection;
77
use Nuwave\Lighthouse\Schema\Values\FieldValue;
8+
use Nuwave\Lighthouse\Select\SelectHelper;
89
use Nuwave\Lighthouse\Support\Contracts\FieldResolver;
910
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
1011

@@ -35,13 +36,20 @@ public function resolveField(FieldValue $fieldValue): FieldValue
3536
{
3637
return $fieldValue->setResolver(
3738
function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Collection {
38-
return $resolveInfo
39+
$builder = $resolveInfo
3940
->argumentSet
4041
->enhanceBuilder(
4142
$this->getModelClass()::query(),
4243
$this->directiveArgValue('scopes', [])
43-
)
44-
->get();
44+
);
45+
46+
if (config('lighthouse.optimized_selects')) {
47+
$fieldSelection = array_keys($resolveInfo->getFieldSelection(1));
48+
$selectColumns = SelectHelper::getSelectColumns($this->definitionNode, $fieldSelection, $this->getModelClass());
49+
$builder = $builder->select($selectColumns);
50+
}
51+
52+
return $builder->get();
4553
}
4654
);
4755
}

src/Schema/Directives/RelationDirective.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Nuwave\Lighthouse\Pagination\PaginationType;
1717
use Nuwave\Lighthouse\Schema\AST\DocumentAST;
1818
use Nuwave\Lighthouse\Schema\Values\FieldValue;
19+
use Nuwave\Lighthouse\Select\SelectHelper;
1920
use Nuwave\Lighthouse\Support\Contracts\FieldResolver;
2021
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
2122

@@ -27,7 +28,7 @@ public function resolveField(FieldValue $value): FieldValue
2728
function (Model $parent, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) {
2829
$relationName = $this->directiveArgValue('relation', $this->nodeName());
2930

30-
$decorateBuilder = $this->makeBuilderDecorator($resolveInfo);
31+
$decorateBuilder = $this->makeBuilderDecorator($resolveInfo, $parent, $relationName);
3132
$paginationArgs = $this->paginationArgs($args);
3233

3334
if (config('lighthouse.batchload_relations')) {
@@ -70,15 +71,27 @@ function (Model $parent, array $args, GraphQLContext $context, ResolveInfo $reso
7071
return $value;
7172
}
7273

73-
protected function makeBuilderDecorator(ResolveInfo $resolveInfo): Closure
74+
protected function makeBuilderDecorator(ResolveInfo $resolveInfo, Model $parent, string $relationName): Closure
7475
{
75-
return function ($builder) use ($resolveInfo) {
76-
$resolveInfo
76+
return function ($builder) use ($resolveInfo, $parent, $relationName) {
77+
$builderDecorator = $resolveInfo
7778
->argumentSet
7879
->enhanceBuilder(
7980
$builder,
8081
$this->directiveArgValue('scopes', [])
8182
);
83+
84+
if (config('lighthouse.optimized_selects')) {
85+
$fieldSelection = array_keys($resolveInfo->getFieldSelection(1));
86+
$selectColumns = SelectHelper::getSelectColumns($this->definitionNode, $fieldSelection, $this->getModelClass());
87+
$foreignKeyName = $parent->{$relationName}()->getForeignKeyName();
88+
89+
if (! in_array($foreignKeyName, $selectColumns)) {
90+
array_push($selectColumns, $foreignKeyName);
91+
}
92+
93+
$builderDecorator->select($selectColumns);
94+
}
8295
};
8396
}
8497

src/Select/SelectDirective.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Nuwave\Lighthouse\Select;
4+
5+
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
6+
7+
class SelectDirective extends BaseDirective
8+
{
9+
public static function definition(): string
10+
{
11+
return /** @lang GraphQL */ <<<'GRAPHQL'
12+
"""
13+
Specify the SQL column dependencies of this field.
14+
"""
15+
directive @select(
16+
"""
17+
SQL columns names to pass to the Eloquent query builder
18+
"""
19+
columns: [String!]
20+
) on FIELD_DEFINITION
21+
GRAPHQL;
22+
}
23+
}

src/Select/SelectHelper.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace Nuwave\Lighthouse\Select;
4+
5+
use GraphQL\Language\AST\Node;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Nuwave\Lighthouse\Schema\AST\ASTBuilder;
8+
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
9+
10+
class SelectHelper
11+
{
12+
/**
13+
* Given a field definition node, resolve info, and a model name, return the SQL columns that should be selected.
14+
* Accounts for relationships and the rename and select directives.
15+
*
16+
* @param mixed[] $fieldSelection
17+
* @return string[]
18+
*/
19+
public static function getSelectColumns(Node $definitionNode, array $fieldSelection, string $modelName): array
20+
{
21+
$returnTypeName = ASTHelper::getUnderlyingTypeName($definitionNode);
22+
23+
/** @var \Nuwave\Lighthouse\Schema\AST\DocumentAST $documentAST */
24+
$documentAST = app(ASTBuilder::class)->documentAST();
25+
26+
$type = $documentAST->types[$returnTypeName];
27+
28+
/** @var iterable<\GraphQL\Language\AST\FieldDefinitionNode> $fieldDefinitions */
29+
$fieldDefinitions = $type->fields;
30+
31+
$model = new $modelName;
32+
33+
$selectColumns = [];
34+
35+
foreach ($fieldSelection as $field) {
36+
$fieldDefinition = ASTHelper::firstByName($fieldDefinitions, $field);
37+
38+
if ($fieldDefinition) {
39+
$name = $fieldDefinition->name->value;
40+
41+
if (ASTHelper::hasDirective($fieldDefinition, 'select')) {
42+
// append selected columns in select directive to seletion
43+
$directive = ASTHelper::directiveDefinition($fieldDefinition, 'select');
44+
45+
if ($directive) {
46+
$selectFields = ASTHelper::directiveArgValue($directive, 'columns') ?? [];
47+
$selectColumns = array_merge($selectColumns, $selectFields);
48+
}
49+
} elseif (ASTHelper::hasDirective($fieldDefinition, 'rename')) {
50+
// append renamed attribute to selection
51+
$directive = ASTHelper::directiveDefinition($fieldDefinition, 'rename');
52+
53+
if ($directive) {
54+
$renamedAttribute = ASTHelper::directiveArgValue($directive, 'attribute');
55+
array_push($selectColumns, $renamedAttribute);
56+
}
57+
} elseif (ASTHelper::hasDirective($fieldDefinition, 'count')) {
58+
// append relationship local key
59+
$directive = ASTHelper::directiveDefinition($fieldDefinition, 'count');
60+
61+
if ($directive) {
62+
$relationName = ASTHelper::directiveArgValue($directive, 'relation', $name);
63+
64+
if ($relationName) {
65+
array_push($selectColumns, $model->{$relationName}()->getLocalKeyName());
66+
}
67+
}
68+
} elseif (ASTHelper::hasDirective($fieldDefinition, 'hasOne')) {
69+
// append relationship local key
70+
$directive = ASTHelper::directiveDefinition($fieldDefinition, 'hasOne');
71+
72+
if ($directive) {
73+
$relationName = ASTHelper::directiveArgValue($directive, 'relation', $name);
74+
array_push($selectColumns, $model->{$relationName}()->getLocalKeyName());
75+
}
76+
} elseif (ASTHelper::hasDirective($fieldDefinition, 'hasMany')) {
77+
// append relationship local key
78+
$directive = ASTHelper::directiveDefinition($fieldDefinition, 'hasMany');
79+
80+
if ($directive) {
81+
$relationName = ASTHelper::directiveArgValue($directive, 'relation', $name);
82+
array_push($selectColumns, $model->{$relationName}()->getLocalKeyName());
83+
}
84+
} elseif (ASTHelper::hasDirective($fieldDefinition, 'belongsTo')) {
85+
// append relationship foreign key
86+
$directive = ASTHelper::directiveDefinition($fieldDefinition, 'belongsTo');
87+
88+
if ($directive) {
89+
$relationName = ASTHelper::directiveArgValue($directive, 'relation', $name);
90+
array_push($selectColumns, $model->{$relationName}()->getForeignKeyName());
91+
}
92+
} elseif (ASTHelper::hasDirective($fieldDefinition, 'belongsToMany')) {
93+
// append relationship foreign key
94+
$directive = ASTHelper::directiveDefinition($fieldDefinition, 'belongsToMany');
95+
96+
if ($directive) {
97+
$relationName = ASTHelper::directiveArgValue($directive, 'relation', $name);
98+
array_push($selectColumns, $model->{$relationName}()->getForeignKeyName());
99+
}
100+
} else {
101+
// fallback to selecting the field name
102+
array_push($selectColumns, $name);
103+
}
104+
}
105+
}
106+
107+
return array_unique($selectColumns);
108+
}
109+
}

src/lighthouse.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,19 @@
295295

296296
'batchload_relations' => true,
297297

298+
/*
299+
|--------------------------------------------------------------------------
300+
| Optimized Selects
301+
|--------------------------------------------------------------------------
302+
|
303+
| If set to true, Eloquent will only select the columns neccessary to resolve
304+
| a query. You must use the select directive to resolve advanced field dependencies
305+
| on other columns.
306+
|
307+
*/
308+
309+
'optimized_selects' => true,
310+
298311
/*
299312
|--------------------------------------------------------------------------
300313
| GraphQL Subscriptions

0 commit comments

Comments
 (0)