Skip to content

Commit

Permalink
[9.x] Cursor pagination: convert original column to expression (#41003)
Browse files Browse the repository at this point in the history
* Convert original column to expression

* Update tests

* Check if original column name is expression

* use str contains

Co-authored-by: Taylor Otwell <taylor@laravel.com>
  • Loading branch information
gdebrauwer and taylorotwell authored Feb 16, 2022
1 parent 3a9d609 commit bf90720
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 3 deletions.
12 changes: 9 additions & 3 deletions src/Illuminate/Database/Concerns/BuildsQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\MultipleRecordsFoundException;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\RecordsNotFoundException;
use Illuminate\Pagination\Cursor;
use Illuminate\Pagination\CursorPaginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
use InvalidArgumentException;
use RuntimeException;
Expand Down Expand Up @@ -340,8 +342,10 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName =
if (! is_null($cursor)) {
$addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) {
if (! is_null($previousColumn)) {
$originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $previousColumn);

$builder->where(
$this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn,
'=',
$cursor->parameter($previousColumn)
);
Expand All @@ -350,8 +354,10 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName =
$builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i) {
['column' => $column, 'direction' => $direction] = $orders[$i];

$originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $column);

$builder->where(
$this->getOriginalColumnNameForCursorPagination($this, $column),
Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn,
$direction === 'asc' ? '>' : '<',
$cursor->parameter($column)
);
Expand Down Expand Up @@ -394,7 +400,7 @@ protected function getOriginalColumnNameForCursorPagination($builder, string $pa

[$original, $alias] = explode($as, $column);

if ($parameter === $alias) {
if ($parameter === $alias || $builder->getGrammar()->wrap($parameter) === $alias) {
return $original;
}
}
Expand Down
82 changes: 82 additions & 0 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4111,6 +4111,88 @@ public function testCursorPaginateWithMixedOrders()
]), $result);
}

public function testCursorPaginateWithDynamicColumnInSelectRaw()
{
$perPage = 15;
$cursorName = 'cursor';
$cursor = new Cursor(['test' => 'bar']);
$builder = $this->getMockQueryBuilder();
$builder->from('foobar')->select('*')->selectRaw('(CONCAT(firstname, \' \', lastname)) as test')->orderBy('test');
$builder->shouldReceive('newQuery')->andReturnUsing(function () use ($builder) {
return new Builder($builder->connection, $builder->grammar, $builder->processor);
});

$path = 'http://foo.bar?cursor='.$cursor->encode();

$results = collect([['test' => 'foo'], ['test' => 'bar']]);

$builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results) {
$this->assertEquals(
'select *, (CONCAT(firstname, \' \', lastname)) as test from "foobar" where ((CONCAT(firstname, \' \', lastname)) > ?) order by "test" asc limit 16',
$builder->toSql());
$this->assertEquals(['bar'], $builder->bindings['where']);

return $results;
});

CursorPaginator::currentCursorResolver(function () use ($cursor) {
return $cursor;
});

Paginator::currentPathResolver(function () use ($path) {
return $path;
});

$result = $builder->cursorPaginate();

$this->assertEquals(new CursorPaginator($results, $perPage, $cursor, [
'path' => $path,
'cursorName' => $cursorName,
'parameters' => ['test'],
]), $result);
}

public function testCursorPaginateWithDynamicColumnInSelectSub()
{
$perPage = 15;
$cursorName = 'cursor';
$cursor = new Cursor(['test' => 'bar']);
$builder = $this->getMockQueryBuilder();
$builder->from('foobar')->select('*')->selectSub('CONCAT(firstname, \' \', lastname)', 'test')->orderBy('test');
$builder->shouldReceive('newQuery')->andReturnUsing(function () use ($builder) {
return new Builder($builder->connection, $builder->grammar, $builder->processor);
});

$path = 'http://foo.bar?cursor='.$cursor->encode();

$results = collect([['test' => 'foo'], ['test' => 'bar']]);

$builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results) {
$this->assertEquals(
'select *, (CONCAT(firstname, \' \', lastname)) as "test" from "foobar" where ((CONCAT(firstname, \' \', lastname)) > ?) order by "test" asc limit 16',
$builder->toSql());
$this->assertEquals(['bar'], $builder->bindings['where']);

return $results;
});

CursorPaginator::currentCursorResolver(function () use ($cursor) {
return $cursor;
});

Paginator::currentPathResolver(function () use ($path) {
return $path;
});

$result = $builder->cursorPaginate();

$this->assertEquals(new CursorPaginator($results, $perPage, $cursor, [
'path' => $path,
'cursorName' => $cursorName,
'parameters' => ['test'],
]), $result);
}

public function testWhereRowValues()
{
$builder = $this->getBuilder();
Expand Down

0 comments on commit bf90720

Please sign in to comment.