-
-
Notifications
You must be signed in to change notification settings - Fork 438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support optimized select for top-level query #2235
base: master
Are you sure you want to change the base?
Changes from all commits
2f13537
290e464
03d4124
cacc576
d49f0ec
6f1a04a
ed0bee9
3ef48bd
23237a1
1b73b78
bcf8f8c
6ca50ae
c9a7343
7e75f24
45d12f1
cd30eea
fa6f99a
7bc7c76
f983dec
25e0ecc
abf875f
61ed5cd
768b28b
6e21e28
0990919
deabb67
667f222
6b456f6
c3ff536
9ec7c71
ee2442f
3221dd2
982f4c1
5cd2304
e5b0715
2d9f386
aafd02c
f0c4846
5171228
1d570bf
f385f5d
b170698
2aa0e4a
f4fb5d8
24ee8d4
0635733
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,17 +2,20 @@ | |
|
||
namespace Nuwave\Lighthouse\Pagination; | ||
|
||
use GraphQL\Error\Error; | ||
use GraphQL\Language\AST\FieldDefinitionNode; | ||
use GraphQL\Language\AST\ObjectTypeDefinitionNode; | ||
use Illuminate\Contracts\Pagination\Paginator; | ||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; | ||
use Illuminate\Database\Eloquent\Relations\Relation; | ||
use Illuminate\Database\Query\Builder as QueryBuilder; | ||
use Illuminate\Support\Arr; | ||
use Laravel\Scout\Builder as ScoutBuilder; | ||
use Nuwave\Lighthouse\Execution\ResolveInfo; | ||
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; | ||
|
@@ -156,6 +159,45 @@ public function resolveField(FieldValue $fieldValue): FieldValue | |
$this->directiveArgValue('scopes', []) | ||
); | ||
|
||
if (config('lighthouse.optimized_selects')) { | ||
if ($query instanceof EloquentBuilder) { | ||
$fieldSelection = $resolveInfo->getFieldSelection(2); | ||
|
||
if (($hasData = Arr::has($fieldSelection, 'data')) || Arr::has($fieldSelection, 'edges')) { | ||
$data = $hasData | ||
? $fieldSelection['data'] | ||
: $fieldSelection['edges']['node']; | ||
|
||
/** @var array<int, string> $fieldSelection */ | ||
$fieldSelection = array_keys($data); | ||
|
||
$model = $query->getModel(); | ||
|
||
$selectColumns = SelectHelper::getSelectColumns( | ||
$this->definitionNode, | ||
$fieldSelection, | ||
get_class($model) | ||
); | ||
|
||
if (empty($selectColumns)) { | ||
throw new Error('The select column is empty.'); | ||
} | ||
|
||
$query = $query->select($selectColumns); | ||
|
||
/** @var string|string[] $keyName */ | ||
$keyName = $model->getKeyName(); | ||
if (is_string($keyName)) { | ||
$keyName = [$keyName]; | ||
} | ||
|
||
foreach ($keyName as $name) { | ||
$query->orderBy($name); | ||
} | ||
Comment on lines
+188
to
+196
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be unrelated to optimizing the select. |
||
} | ||
} | ||
} | ||
|
||
$paginationArgs = PaginationArgs::extractArgs($args, $this->paginationType(), $this->paginateMaxCount()); | ||
|
||
$paginationArgs->type = $this->optimalPaginationType($resolveInfo); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
|
||
use GraphQL\Language\AST\FieldDefinitionNode; | ||
use GraphQL\Language\AST\NamedTypeNode; | ||
use GraphQL\Language\AST\Node; | ||
use GraphQL\Language\AST\NonNullTypeNode; | ||
use GraphQL\Language\AST\ObjectTypeDefinitionNode; | ||
use GraphQL\Language\AST\TypeNode; | ||
|
@@ -15,6 +16,8 @@ | |
use Nuwave\Lighthouse\Schema\AST\DocumentAST; | ||
use Nuwave\Lighthouse\Schema\Directives\ModelDirective; | ||
|
||
use function Safe\preg_replace; | ||
|
||
class PaginationManipulator | ||
{ | ||
/** | ||
|
@@ -273,4 +276,14 @@ protected function paginationResultType(string $typeName): TypeNode | |
|
||
return $typeNode; | ||
} | ||
|
||
public static function getReturnTypeName(Node $fieldDefinition): ?string | ||
{ | ||
if (! ASTHelper::directiveDefinition($fieldDefinition, 'paginate')) { | ||
return null; | ||
} | ||
$fieldTypeName = ASTHelper::getUnderlyingTypeName($fieldDefinition); | ||
|
||
return preg_replace('/(Connection|SimplePaginator|Paginator)$/', '', $fieldTypeName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can look at the argument of |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,16 +2,19 @@ | |
|
||
namespace Nuwave\Lighthouse\Schema\Directives; | ||
|
||
use GraphQL\Error\Error; | ||
use GraphQL\Language\AST\FieldDefinitionNode; | ||
use GraphQL\Language\AST\ObjectTypeDefinitionNode; | ||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; | ||
use Illuminate\Database\Eloquent\Relations\Relation; | ||
use Illuminate\Database\Query\Builder as QueryBuilder; | ||
use Illuminate\Database\Query\Expression; | ||
use Illuminate\Support\Collection; | ||
use Laravel\Scout\Builder as ScoutBuilder; | ||
use Nuwave\Lighthouse\Execution\ResolveInfo; | ||
use Nuwave\Lighthouse\Schema\AST\DocumentAST; | ||
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; | ||
|
@@ -63,12 +66,57 @@ public function resolveField(FieldValue $fieldValue): FieldValue | |
$query = $this->getModelClass()::query(); | ||
} | ||
|
||
return $resolveInfo | ||
$builder = $resolveInfo | ||
->enhanceBuilder( | ||
$query, | ||
$this->directiveArgValue('scopes', []) | ||
) | ||
->get(); | ||
); | ||
|
||
if (config('lighthouse.optimized_selects')) { | ||
if ($builder instanceof EloquentBuilder) { | ||
$fieldSelection = array_keys($resolveInfo->getFieldSelection(1)); | ||
|
||
$model = $builder->getModel(); | ||
|
||
$selectColumns = SelectHelper::getSelectColumns( | ||
$this->definitionNode, | ||
$fieldSelection, | ||
get_class($model) | ||
); | ||
|
||
if (empty($selectColumns)) { | ||
throw new Error('The select column is empty.'); | ||
} | ||
|
||
$query = $builder->getQuery(); | ||
|
||
if (null !== $query->columns) { | ||
$bindings = $query->getRawBindings(); | ||
|
||
$expressions = array_filter($query->columns, function ($column) { | ||
return $column instanceof Expression; | ||
}); | ||
|
||
$builder = $builder->select(array_unique(array_merge($selectColumns, $expressions))); | ||
|
||
$builder = $builder->addBinding($bindings['select'], 'select'); | ||
} else { | ||
$builder = $builder->select($selectColumns); | ||
} | ||
|
||
/** @var string|string[] $keyName */ | ||
$keyName = $model->getKeyName(); | ||
if (is_string($keyName)) { | ||
$keyName = [$keyName]; | ||
} | ||
|
||
foreach ($keyName as $name) { | ||
$query->orderBy($name); | ||
} | ||
Comment on lines
+107
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, this seems like an unnecessary addition that is orthogonal to optimizing select. |
||
} | ||
} | ||
|
||
return $builder->get(); | ||
}); | ||
|
||
return $fieldValue; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
use Illuminate\Database\Eloquent\Model; | ||
use Nuwave\Lighthouse\Execution\ResolveInfo; | ||
use Nuwave\Lighthouse\Schema\Values\FieldValue; | ||
use Nuwave\Lighthouse\Select\SelectHelper; | ||
use Nuwave\Lighthouse\Support\Contracts\FieldResolver; | ||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; | ||
|
||
|
@@ -35,12 +36,41 @@ public static function definition(): string | |
public function resolveField(FieldValue $fieldValue): FieldValue | ||
{ | ||
$fieldValue->setResolver(function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): ?Model { | ||
$results = $resolveInfo | ||
$builder = $resolveInfo | ||
->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() | ||
); | ||
|
||
if (empty($selectColumns)) { | ||
throw new Error('The select column is empty.'); | ||
} | ||
|
||
$builder = $builder->select($selectColumns); | ||
|
||
$model = $builder->getModel(); | ||
|
||
/** @var string|string[] $keyName */ | ||
$keyName = $model->getKeyName(); | ||
if (is_string($keyName)) { | ||
$keyName = [$keyName]; | ||
} | ||
|
||
foreach ($keyName as $name) { | ||
$builder->orderBy($name); | ||
} | ||
Comment on lines
+61
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unnecessary |
||
} | ||
|
||
$results = $builder->get(); | ||
|
||
if ($results->count() > 1) { | ||
throw new Error('The query returned more than one result.'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
namespace Nuwave\Lighthouse\Schema\Directives; | ||
|
||
class SelectDirective extends BaseDirective | ||
{ | ||
public static function definition(): string | ||
{ | ||
return /** @lang GraphQL */ <<<'GRAPHQL' | ||
""" | ||
Specify the SQL column dependencies of this field. | ||
""" | ||
directive @select( | ||
""" | ||
SQL column names to include in the `SELECT` part of the query. | ||
""" | ||
columns: [String!]! | ||
) on FIELD_DEFINITION | ||
GRAPHQL; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can derive which field to check from
$this->directiveArgValue('type')
, no need to check both.