diff --git a/src/Illuminate/Database/Concerns/BuildsQueries.php b/src/Illuminate/Database/Concerns/BuildsQueries.php index b2f973afb18e..c41a58b32687 100644 --- a/src/Illuminate/Database/Concerns/BuildsQueries.php +++ b/src/Illuminate/Database/Concerns/BuildsQueries.php @@ -382,11 +382,11 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = // Reset the union bindings so we can add the cursor where in the correct position... $this->setBindings([], 'union'); - $addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) { + $addCursorConditions = function (self $builder, $previousColumn, $originalColumn, $i) use (&$addCursorConditions, $cursor, $orders) { $unionBuilders = $builder->getUnionBuilders(); if (! is_null($previousColumn)) { - $originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $previousColumn); + $originalColumn ??= $this->getOriginalColumnNameForCursorPagination($this, $previousColumn); $builder->where( Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn, @@ -396,7 +396,7 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = $unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) { $unionBuilder->where( - $this->getOriginalColumnNameForCursorPagination($this, $previousColumn), + $this->getOriginalColumnNameForCursorPagination($unionBuilder, $previousColumn), '=', $cursor->parameter($previousColumn) ); @@ -417,24 +417,25 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = ); if ($i < $orders->count() - 1) { - $secondBuilder->orWhere(function (self $thirdBuilder) use ($addCursorConditions, $column, $i) { - $addCursorConditions($thirdBuilder, $column, $i + 1); + $secondBuilder->orWhere(function (self $thirdBuilder) use ($addCursorConditions, $column, $originalColumn, $i) { + $addCursorConditions($thirdBuilder, $column, $originalColumn, $i + 1); }); } $unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) { $unionWheres = $unionBuilder->getRawBindings()['where']; - $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions, $unionWheres) { + $originalColumn = $this->getOriginalColumnNameForCursorPagination($unionBuilder, $column); + $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions, $originalColumn, $unionWheres) { $unionBuilder->where( - $this->getOriginalColumnNameForCursorPagination($this, $column), + $originalColumn, $direction === 'asc' ? '>' : '<', $cursor->parameter($column) ); if ($i < $orders->count() - 1) { - $unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $i) { - $addCursorConditions($fourthBuilder, $column, $i + 1); + $unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $originalColumn, $i) { + $addCursorConditions($fourthBuilder, $column, $originalColumn, $i + 1); }); } @@ -445,7 +446,7 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = }); }; - $addCursorConditions($this, null, 0); + $addCursorConditions($this, null, null, 0); } $this->limit($perPage + 1); diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 90125356bdd8..39f49f45d53c 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -5276,7 +5276,7 @@ public function testCursorPaginateWithUnionWheres() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(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', + '(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', $builder->toSql()); $this->assertEquals([$ts], $builder->bindings['where']); $this->assertEquals([$ts], $builder->bindings['union']); @@ -5325,7 +5325,7 @@ public function testCursorPaginateWithMultipleUnionsAndMultipleWheres() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(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', + '(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', $builder->toSql()); $this->assertEquals([$ts], $builder->bindings['where']); $this->assertEquals(['first', $ts, 'second', $ts], $builder->bindings['union']); @@ -5422,7 +5422,7 @@ public function testCursorPaginateWithUnionWheresWithRawOrderExpression() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(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', + '(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', $builder->toSql()); $this->assertEquals([true, $ts], $builder->bindings['where']); $this->assertEquals([true, $ts], $builder->bindings['union']); @@ -5469,7 +5469,7 @@ public function testCursorPaginateWithUnionWheresReverseOrder() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(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', + '(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', $builder->toSql()); $this->assertEquals([$ts], $builder->bindings['where']); $this->assertEquals([$ts], $builder->bindings['union']); @@ -5516,7 +5516,7 @@ public function testCursorPaginateWithUnionWheresMultipleOrders() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(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', + '(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', $builder->toSql()); $this->assertEquals([$ts, $ts, 1], $builder->bindings['where']); $this->assertEquals([$ts, $ts, 1], $builder->bindings['union']); @@ -5537,6 +5537,55 @@ public function testCursorPaginateWithUnionWheresMultipleOrders() ]), $result); } + public function testCursorPaginateWithUnionWheresAndAliassedOrderColumns() + { + $ts = now()->toDateTimeString(); + + $perPage = 16; + $columns = ['test']; + $cursorName = 'cursor-name'; + $cursor = new Cursor(['created_at' => $ts]); + $builder = $this->getMockQueryBuilder(); + $builder->select('id', 'start_time as created_at')->selectRaw("'video' as type")->from('videos'); + $builder->union($this->getBuilder()->select('id', 'created_at')->selectRaw("'news' as type")->from('news')); + $builder->union($this->getBuilder()->select('id', 'init_at as created_at')->selectRaw("'podcast' as type")->from('podcasts')); + $builder->orderBy('created_at'); + + $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([ + ['id' => 1, 'created_at' => now(), 'type' => 'video'], + ['id' => 2, 'created_at' => now(), 'type' => 'news'], + ['id' => 3, 'created_at' => now(), 'type' => 'podcast'], + ]); + + $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { + $this->assertEquals( + '(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', + $builder->toSql()); + $this->assertEquals([$ts], $builder->bindings['where']); + $this->assertEquals([$ts, $ts], $builder->bindings['union']); + + return $results; + }); + + Paginator::currentPathResolver(function () use ($path) { + return $path; + }); + + $result = $builder->cursorPaginate($perPage, $columns, $cursorName, $cursor); + + $this->assertEquals(new CursorPaginator($results, $perPage, $cursor, [ + 'path' => $path, + 'cursorName' => $cursorName, + 'parameters' => ['created_at'], + ]), $result); + } + public function testWhereExpression() { $builder = $this->getBuilder(); diff --git a/tests/Integration/Database/EloquentCursorPaginateTest.php b/tests/Integration/Database/EloquentCursorPaginateTest.php index 02034c485632..54480f90bf76 100644 --- a/tests/Integration/Database/EloquentCursorPaginateTest.php +++ b/tests/Integration/Database/EloquentCursorPaginateTest.php @@ -21,6 +21,7 @@ protected function afterRefreshingDatabase() Schema::create('test_users', function ($table) { $table->increments('id'); + $table->string('name')->nullable(); $table->timestamps(); }); } @@ -195,6 +196,32 @@ public function testPaginationWithMultipleUnionAndMultipleWhereClauses() $this->assertEquals('Post B', current($result->items())->title, 'Expect the paginated query would return `Post B`'); } + public function testPaginationWithMultipleAliases() + { + TestUser::create(['name' => 'A (user)']); + TestUser::create(['name' => 'C (user)']); + + TestPost::create(['title' => 'B (post)']); + TestPost::create(['title' => 'D (post)']); + + $table1 = TestPost::select(['title as alias']); + $table2 = TestUser::select(['name as alias']); + + $columns = ['alias']; + $cursorName = 'cursor-name'; + $cursor = new Cursor(['alias' => 'A (user)']); + + $result = $table1->toBase() + ->union($table2->toBase()) + ->orderBy('alias', 'asc') + ->cursorPaginate(1, $columns, $cursorName, $cursor); + + $this->assertSame(['alias'], $result->getOptions()['parameters']); + + $this->assertCount(1, $result->items(), 'Expect cursor paginated query should have 1 result'); + $this->assertEquals('B (post)', current($result->items())->alias, 'Expect the paginated query would return `B (post)`'); + } + public function testPaginationWithAliasedOrderBy() { for ($i = 1; $i <= 6; $i++) {