Skip to content

Commit

Permalink
Add option builder to @limit to apply an actual LIMIT clause
Browse files Browse the repository at this point in the history
  • Loading branch information
spawnia authored Jun 19, 2024
1 parent 48c0dbf commit d15b988
Show file tree
Hide file tree
Showing 19 changed files with 206 additions and 37 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

## v6.38.0

### Added

- Add option `builder` to `@limit` to apply an actual `LIMIT` clause https://github.com/nuwave/lighthouse/pull/2571

## v6.37.1

### Fixed
Expand Down
31 changes: 24 additions & 7 deletions docs/6/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -2021,15 +2021,23 @@ directive @like(
```graphql
"""
Allow clients to specify the maximum number of results to return when used on an argument,
or statically limits them when used on a field.
or statically limit them when used on a field.

This directive does not influence the number of results the resolver queries internally,
but limits how much of it is returned to clients.
By default, this directive does not influence the number of results the resolver queries internally,
but limits how much of it is returned to clients. Use the `builder` argument to change this.
"""
directive @limit on ARGUMENT_DEFINITION | FIELD_DEFINITION
directive @limit(
"""
You may set this to `true` if the field uses a query builder,
then this directive will apply a LIMIT clause to it.
Typically, this option should only be used for root fields,
as it may cause wrong results with batched relation queries.
"""
builder: Boolean! = false
) on ARGUMENT_DEFINITION | FIELD_DEFINITION
```
Place this on any argument to a field that returns a list of results.
You may place this on any argument to a field that returns a list of results.
```graphql
type Query {
Expand All @@ -2041,7 +2049,7 @@ Lighthouse will return at most the number of results that the client requested.
```graphql
{
users(limit: 5) {
users(limit: 4) {
name
}
}
Expand All @@ -2054,12 +2062,21 @@ Lighthouse will return at most the number of results that the client requested.
{ "name": "Never" },
{ "name": "more" },
{ "name": "than" },
{ "name": "5" }
{ "name": "4" }
]
}
}
```
If your field is resolved through a database query, you may add the `builder` argument to apply
an actual `LIMIT` clause to your SQL:
```graphql
type Query {
users(limit: Int @limit(builder: true)): [User!]! @all
}
```
## @method
```graphql
Expand Down
2 changes: 1 addition & 1 deletion docs/6/custom-directives/input-value-directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ An [`\Nuwave\Lighthouse\Support\Contracts\ArgBuilderDirective`](https://github.c
directive allows using arguments passed by the client to dynamically
modify the database query that Lighthouse creates for a field.

Currently, the following directives use the defined filters for resolving the query:
Currently, the following directives use arguments to modify the query:

- [@all](../api-reference/directives.md#all)
- [@paginate](../api-reference/directives.md#paginate)
Expand Down
31 changes: 24 additions & 7 deletions docs/master/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -2021,15 +2021,23 @@ directive @like(
```graphql
"""
Allow clients to specify the maximum number of results to return when used on an argument,
or statically limits them when used on a field.
or statically limit them when used on a field.

This directive does not influence the number of results the resolver queries internally,
but limits how much of it is returned to clients.
By default, this directive does not influence the number of results the resolver queries internally,
but limits how much of it is returned to clients. Use the `builder` argument to change this.
"""
directive @limit on ARGUMENT_DEFINITION | FIELD_DEFINITION
directive @limit(
"""
You may set this to `true` if the field uses a query builder,
then this directive will apply a LIMIT clause to it.
Typically, this option should only be used for root fields,
as it may cause wrong results with batched relation queries.
"""
builder: Boolean! = false
) on ARGUMENT_DEFINITION | FIELD_DEFINITION
```
Place this on any argument to a field that returns a list of results.
You may place this on any argument to a field that returns a list of results.
```graphql
type Query {
Expand All @@ -2041,7 +2049,7 @@ Lighthouse will return at most the number of results that the client requested.
```graphql
{
users(limit: 5) {
users(limit: 4) {
name
}
}
Expand All @@ -2054,12 +2062,21 @@ Lighthouse will return at most the number of results that the client requested.
{ "name": "Never" },
{ "name": "more" },
{ "name": "than" },
{ "name": "5" }
{ "name": "4" }
]
}
}
```
If your field is resolved through a database query, you may add the `builder` argument to apply
an actual `LIMIT` clause to your SQL:
```graphql
type Query {
users(limit: Int @limit(builder: true)): [User!]! @all
}
```
## @method
```graphql
Expand Down
2 changes: 1 addition & 1 deletion docs/master/custom-directives/input-value-directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ An [`\Nuwave\Lighthouse\Support\Contracts\ArgBuilderDirective`](https://github.c
directive allows using arguments passed by the client to dynamically
modify the database query that Lighthouse creates for a field.

Currently, the following directives use the defined filters for resolving the query:
Currently, the following directives use arguments to modify the query:

- [@all](../api-reference/directives.md#all)
- [@paginate](../api-reference/directives.md#paginate)
Expand Down
46 changes: 41 additions & 5 deletions src/Schema/Directives/LimitDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,42 @@
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Type\Definition\Type;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Nuwave\Lighthouse\Exceptions\DefinitionException;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
use Nuwave\Lighthouse\Schema\AST\DocumentAST;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\ArgBuilderDirective;
use Nuwave\Lighthouse\Support\Contracts\ArgDirective;
use Nuwave\Lighthouse\Support\Contracts\ArgManipulator;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
use Nuwave\Lighthouse\Support\Utils;

class LimitDirective extends BaseDirective implements ArgDirective, ArgManipulator, FieldMiddleware
class LimitDirective extends BaseDirective implements ArgDirective, ArgBuilderDirective, ArgManipulator, FieldMiddleware
{
public static function definition(): string
{
return /* @lang GraphQL */ <<<'GRAPHQL'
"""
Allow clients to specify the maximum number of results to return when used on an argument,
or statically limits them when used on a field.
or statically limit them when used on a field.
This directive does not influence the number of results the resolver queries internally,
but limits how much of it is returned to clients.
By default, this directive does not influence the number of results the resolver queries internally,
but limits how much of it is returned to clients. Use the `builder` argument to change this.
"""
directive @limit on ARGUMENT_DEFINITION | FIELD_DEFINITION
directive @limit(
"""
You may set this to `true` if the field uses a query builder,
then this directive will apply a LIMIT clause to it.
Typically, this option should only be used for root fields,
as it may cause wrong results with batched relation queries.
"""
builder: Boolean! = false
) on ARGUMENT_DEFINITION | FIELD_DEFINITION
GRAPHQL;
}

Expand All @@ -40,6 +52,10 @@ public function manipulateArgDefinition(
FieldDefinitionNode &$parentField,
ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode &$parentType,
): void {
if ($this->shouldApplyToQueryBuilders()) {
return;
}

$argType = ASTHelper::getUnderlyingTypeName($argDefinition->type);
$expectedArgType = Type::INT;
if ($expectedArgType !== $argType) {
Expand All @@ -51,6 +67,10 @@ public function manipulateArgDefinition(

public function handleField(FieldValue $fieldValue): void
{
if ($this->shouldApplyToQueryBuilders()) {
return;
}

$fieldValue->resultHandler(static function (?iterable $result, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): ?iterable {
if ($result === null) {
return null;
Expand Down Expand Up @@ -88,4 +108,20 @@ public function handleField(FieldValue $fieldValue): void
return $limited;
});
}

public function handleBuilder(Relation|EloquentBuilder|QueryBuilder $builder, mixed $value): QueryBuilder|EloquentBuilder|Relation
{
if (! $this->shouldApplyToQueryBuilders()) {
return $builder;
}

assert(is_int($value));

return $builder->limit($value);
}

protected function shouldApplyToQueryBuilders(): bool
{
return $this->directiveArgValue('builder') ?? false;
}
}
2 changes: 1 addition & 1 deletion tests/Integration/Auth/CanFindDirectiveDBTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Auth;
namespace Tests\Integration\Auth;

use GraphQL\Error\Error;
use Nuwave\Lighthouse\Auth\CanDirective;
Expand Down
2 changes: 1 addition & 1 deletion tests/Integration/Auth/CanQueryDirectiveDBTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Auth;
namespace Tests\Integration\Auth;

use Tests\DBTestCase;
use Tests\Utils\Models\Post;
Expand Down
2 changes: 1 addition & 1 deletion tests/Integration/Auth/CanResolvedDirectiveDBTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Auth;
namespace Tests\Integration\Auth;

use Tests\DBTestCase;
use Tests\Utils\Models\Company;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,6 @@ public function testSavesOnlyOnceWithMultipleBelongsTo(): void

$queries = [];
DB::listen(static function (QueryExecuted $query) use (&$queries): void {
dump($query->sql);
$queries[] = $query->sql;
});

Expand Down
2 changes: 1 addition & 1 deletion tests/Integration/Schema/Directives/InDirectiveTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Schema\Directives;
namespace Tests\Integration\Schema\Directives;

use Tests\DBTestCase;
use Tests\Utils\Models\User;
Expand Down
44 changes: 44 additions & 0 deletions tests/Integration/Schema/Directives/LimitDirectiveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace Tests\Integration\Schema\Directives;

use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Facades\DB;
use Nuwave\Lighthouse\Cache\CacheKeyAndTagsGenerator;
use Tests\DBTestCase;
use Tests\TestsSerialization;
Expand Down Expand Up @@ -34,6 +36,45 @@ public function testLimitsResults(): void
}
';

$queries = [];
DB::listen(static function (QueryExecuted $query) use (&$queries): void {
$queries[] = $query->sql;
});

$limit = 1;
$this->graphQL(/** @lang GraphQL */ '
query ($limit: Int) {
users(limit: $limit) {
id
}
}
', [
'limit' => $limit,
])->assertJsonCount($limit, 'data.users');
$this->assertSame([
'select * from `users`',
], $queries);
}

public function testLimitsQueryBuilder(): void
{
factory(User::class, 2)->create();

$this->schema = /** @lang GraphQL */ '
type User {
id: ID!
}
type Query {
users(limit: Int @limit(builder: true)): [User!]! @all
}
';

$queries = [];
DB::listen(static function (QueryExecuted $query) use (&$queries): void {
$queries[] = $query->sql;
});

$limit = 1;
$this->graphQL(/** @lang GraphQL */ '
query ($limit: Int) {
Expand All @@ -44,6 +85,9 @@ public function testLimitsResults(): void
', [
'limit' => $limit,
])->assertJsonCount($limit, 'data.users');
$this->assertSame([
"select * from `users` limit {$limit}",
], $queries);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/Integration/Schema/Directives/NotInDirectiveTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Schema\Directives;
namespace Tests\Integration\Schema\Directives;

use Tests\DBTestCase;
use Tests\Utils\Models\User;
Expand Down
Loading

0 comments on commit d15b988

Please sign in to comment.