Skip to content

Commit 877ebca

Browse files
authored
[10.x] Fix cursor paginate with union and column alias (#50882)
* Cursor paginate uses incorrect column name for where on union * Set the right column names for the cursor on the unions * Add dedicated test to test correct column alias on union cursor paginate * Add eloquent test for cursor pagination with union and multiple aliases * Use correct column name in cursor where clause
1 parent 67d4d0d commit 877ebca

File tree

3 files changed

+92
-15
lines changed

3 files changed

+92
-15
lines changed

src/Illuminate/Database/Concerns/BuildsQueries.php

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -382,11 +382,11 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName =
382382
// Reset the union bindings so we can add the cursor where in the correct position...
383383
$this->setBindings([], 'union');
384384

385-
$addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) {
385+
$addCursorConditions = function (self $builder, $previousColumn, $originalColumn, $i) use (&$addCursorConditions, $cursor, $orders) {
386386
$unionBuilders = $builder->getUnionBuilders();
387387

388388
if (! is_null($previousColumn)) {
389-
$originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $previousColumn);
389+
$originalColumn ??= $this->getOriginalColumnNameForCursorPagination($this, $previousColumn);
390390

391391
$builder->where(
392392
Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn,
@@ -396,7 +396,7 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName =
396396

397397
$unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) {
398398
$unionBuilder->where(
399-
$this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
399+
$this->getOriginalColumnNameForCursorPagination($unionBuilder, $previousColumn),
400400
'=',
401401
$cursor->parameter($previousColumn)
402402
);
@@ -417,24 +417,25 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName =
417417
);
418418

419419
if ($i < $orders->count() - 1) {
420-
$secondBuilder->orWhere(function (self $thirdBuilder) use ($addCursorConditions, $column, $i) {
421-
$addCursorConditions($thirdBuilder, $column, $i + 1);
420+
$secondBuilder->orWhere(function (self $thirdBuilder) use ($addCursorConditions, $column, $originalColumn, $i) {
421+
$addCursorConditions($thirdBuilder, $column, $originalColumn, $i + 1);
422422
});
423423
}
424424

425425
$unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
426426
$unionWheres = $unionBuilder->getRawBindings()['where'];
427427

428-
$unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions, $unionWheres) {
428+
$originalColumn = $this->getOriginalColumnNameForCursorPagination($unionBuilder, $column);
429+
$unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions, $originalColumn, $unionWheres) {
429430
$unionBuilder->where(
430-
$this->getOriginalColumnNameForCursorPagination($this, $column),
431+
$originalColumn,
431432
$direction === 'asc' ? '>' : '<',
432433
$cursor->parameter($column)
433434
);
434435

435436
if ($i < $orders->count() - 1) {
436-
$unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $i) {
437-
$addCursorConditions($fourthBuilder, $column, $i + 1);
437+
$unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $originalColumn, $i) {
438+
$addCursorConditions($fourthBuilder, $column, $originalColumn, $i + 1);
438439
});
439440
}
440441

@@ -445,7 +446,7 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName =
445446
});
446447
};
447448

448-
$addCursorConditions($this, null, 0);
449+
$addCursorConditions($this, null, null, 0);
449450
}
450451

451452
$this->limit($perPage + 1);

tests/Database/DatabaseQueryBuilderTest.php

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5276,7 +5276,7 @@ public function testCursorPaginateWithUnionWheres()
52765276

52775277
$builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) {
52785278
$this->assertEquals(
5279-
'(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where ("start_time" > ?)) order by "created_at" asc limit 17',
5279+
'(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where ("created_at" > ?)) order by "created_at" asc limit 17',
52805280
$builder->toSql());
52815281
$this->assertEquals([$ts], $builder->bindings['where']);
52825282
$this->assertEquals([$ts], $builder->bindings['union']);
@@ -5325,7 +5325,7 @@ public function testCursorPaginateWithMultipleUnionsAndMultipleWheres()
53255325

53265326
$builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) {
53275327
$this->assertEquals(
5328-
'(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where "extra" = ? and ("start_time" > ?)) union (select "id", "created_at", \'podcast\' as type from "podcasts" where "extra" = ? and ("start_time" > ?)) order by "created_at" asc limit 17',
5328+
'(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where "extra" = ? and ("created_at" > ?)) union (select "id", "created_at", \'podcast\' as type from "podcasts" where "extra" = ? and ("created_at" > ?)) order by "created_at" asc limit 17',
53295329
$builder->toSql());
53305330
$this->assertEquals([$ts], $builder->bindings['where']);
53315331
$this->assertEquals(['first', $ts, 'second', $ts], $builder->bindings['union']);
@@ -5422,7 +5422,7 @@ public function testCursorPaginateWithUnionWheresWithRawOrderExpression()
54225422

54235423
$builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) {
54245424
$this->assertEquals(
5425-
'(select "id", "is_published", "start_time" as "created_at", \'video\' as type from "videos" where "is_published" = ? and ("start_time" > ?)) union (select "id", "is_published", "created_at", \'news\' as type from "news" where "is_published" = ? and ("start_time" > ?)) order by case when (id = 3 and type="news" then 0 else 1 end), "created_at" asc limit 17',
5425+
'(select "id", "is_published", "start_time" as "created_at", \'video\' as type from "videos" where "is_published" = ? and ("start_time" > ?)) union (select "id", "is_published", "created_at", \'news\' as type from "news" where "is_published" = ? and ("created_at" > ?)) order by case when (id = 3 and type="news" then 0 else 1 end), "created_at" asc limit 17',
54265426
$builder->toSql());
54275427
$this->assertEquals([true, $ts], $builder->bindings['where']);
54285428
$this->assertEquals([true, $ts], $builder->bindings['union']);
@@ -5469,7 +5469,7 @@ public function testCursorPaginateWithUnionWheresReverseOrder()
54695469

54705470
$builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) {
54715471
$this->assertEquals(
5472-
'(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" < ?)) union (select "id", "created_at", \'news\' as type from "news" where ("start_time" < ?)) order by "created_at" desc limit 17',
5472+
'(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" < ?)) union (select "id", "created_at", \'news\' as type from "news" where ("created_at" < ?)) order by "created_at" desc limit 17',
54735473
$builder->toSql());
54745474
$this->assertEquals([$ts], $builder->bindings['where']);
54755475
$this->assertEquals([$ts], $builder->bindings['union']);
@@ -5516,7 +5516,7 @@ public function testCursorPaginateWithUnionWheresMultipleOrders()
55165516

55175517
$builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) {
55185518
$this->assertEquals(
5519-
'(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" < ? or ("start_time" = ? and ("id" > ?)))) union (select "id", "created_at", \'news\' as type from "news" where ("start_time" < ? or ("start_time" = ? and ("id" > ?)))) order by "created_at" desc, "id" asc limit 17',
5519+
'(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" < ? or ("start_time" = ? and ("id" > ?)))) union (select "id", "created_at", \'news\' as type from "news" where ("created_at" < ? or ("created_at" = ? and ("id" > ?)))) order by "created_at" desc, "id" asc limit 17',
55205520
$builder->toSql());
55215521
$this->assertEquals([$ts, $ts, 1], $builder->bindings['where']);
55225522
$this->assertEquals([$ts, $ts, 1], $builder->bindings['union']);
@@ -5537,6 +5537,55 @@ public function testCursorPaginateWithUnionWheresMultipleOrders()
55375537
]), $result);
55385538
}
55395539

5540+
public function testCursorPaginateWithUnionWheresAndAliassedOrderColumns()
5541+
{
5542+
$ts = now()->toDateTimeString();
5543+
5544+
$perPage = 16;
5545+
$columns = ['test'];
5546+
$cursorName = 'cursor-name';
5547+
$cursor = new Cursor(['created_at' => $ts]);
5548+
$builder = $this->getMockQueryBuilder();
5549+
$builder->select('id', 'start_time as created_at')->selectRaw("'video' as type")->from('videos');
5550+
$builder->union($this->getBuilder()->select('id', 'created_at')->selectRaw("'news' as type")->from('news'));
5551+
$builder->union($this->getBuilder()->select('id', 'init_at as created_at')->selectRaw("'podcast' as type")->from('podcasts'));
5552+
$builder->orderBy('created_at');
5553+
5554+
$builder->shouldReceive('newQuery')->andReturnUsing(function () use ($builder) {
5555+
return new Builder($builder->connection, $builder->grammar, $builder->processor);
5556+
});
5557+
5558+
$path = 'http://foo.bar?cursor='.$cursor->encode();
5559+
5560+
$results = collect([
5561+
['id' => 1, 'created_at' => now(), 'type' => 'video'],
5562+
['id' => 2, 'created_at' => now(), 'type' => 'news'],
5563+
['id' => 3, 'created_at' => now(), 'type' => 'podcast'],
5564+
]);
5565+
5566+
$builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) {
5567+
$this->assertEquals(
5568+
'(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where ("created_at" > ?)) union (select "id", "init_at" as "created_at", \'podcast\' as type from "podcasts" where ("init_at" > ?)) order by "created_at" asc limit 17',
5569+
$builder->toSql());
5570+
$this->assertEquals([$ts], $builder->bindings['where']);
5571+
$this->assertEquals([$ts, $ts], $builder->bindings['union']);
5572+
5573+
return $results;
5574+
});
5575+
5576+
Paginator::currentPathResolver(function () use ($path) {
5577+
return $path;
5578+
});
5579+
5580+
$result = $builder->cursorPaginate($perPage, $columns, $cursorName, $cursor);
5581+
5582+
$this->assertEquals(new CursorPaginator($results, $perPage, $cursor, [
5583+
'path' => $path,
5584+
'cursorName' => $cursorName,
5585+
'parameters' => ['created_at'],
5586+
]), $result);
5587+
}
5588+
55405589
public function testWhereExpression()
55415590
{
55425591
$builder = $this->getBuilder();

tests/Integration/Database/EloquentCursorPaginateTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ protected function afterRefreshingDatabase()
2121

2222
Schema::create('test_users', function ($table) {
2323
$table->increments('id');
24+
$table->string('name')->nullable();
2425
$table->timestamps();
2526
});
2627
}
@@ -195,6 +196,32 @@ public function testPaginationWithMultipleUnionAndMultipleWhereClauses()
195196
$this->assertEquals('Post B', current($result->items())->title, 'Expect the paginated query would return `Post B`');
196197
}
197198

199+
public function testPaginationWithMultipleAliases()
200+
{
201+
TestUser::create(['name' => 'A (user)']);
202+
TestUser::create(['name' => 'C (user)']);
203+
204+
TestPost::create(['title' => 'B (post)']);
205+
TestPost::create(['title' => 'D (post)']);
206+
207+
$table1 = TestPost::select(['title as alias']);
208+
$table2 = TestUser::select(['name as alias']);
209+
210+
$columns = ['alias'];
211+
$cursorName = 'cursor-name';
212+
$cursor = new Cursor(['alias' => 'A (user)']);
213+
214+
$result = $table1->toBase()
215+
->union($table2->toBase())
216+
->orderBy('alias', 'asc')
217+
->cursorPaginate(1, $columns, $cursorName, $cursor);
218+
219+
$this->assertSame(['alias'], $result->getOptions()['parameters']);
220+
221+
$this->assertCount(1, $result->items(), 'Expect cursor paginated query should have 1 result');
222+
$this->assertEquals('B (post)', current($result->items())->alias, 'Expect the paginated query would return `B (post)`');
223+
}
224+
198225
public function testPaginationWithAliasedOrderBy()
199226
{
200227
for ($i = 1; $i <= 6; $i++) {

0 commit comments

Comments
 (0)