Skip to content

Commit

Permalink
Merge branch '10.x' into 11.x
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
#	src/Illuminate/Foundation/Application.php
  • Loading branch information
driesvints committed Apr 23, 2024
2 parents 0546f75 + ad75850 commit 43e4a9c
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 19 deletions.
33 changes: 20 additions & 13 deletions src/Illuminate/Database/Concerns/BuildsQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,14 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName =
$orders = $this->ensureOrderForCursorPagination(! is_null($cursor) && $cursor->pointsToPreviousItems());

if (! is_null($cursor)) {
$addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) {
$unionBuilders = isset($builder->unions) ? collect($builder->unions)->pluck('query') : collect();
// Reset the union bindings so we can add the cursor where in the correct position...
$this->setBindings([], 'union');

$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,
Expand All @@ -395,7 +398,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)
);
Expand All @@ -404,44 +407,48 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName =
});
}

$builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) {
$builder->where(function (self $secondBuilder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) {
['column' => $column, 'direction' => $direction] = $orders[$i];

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

$builder->where(
$secondBuilder->where(
Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn,
$direction === 'asc' ? '>' : '<',
$cursor->parameter($column)
);

if ($i < $orders->count() - 1) {
$builder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) {
$addCursorConditions($builder, $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) {
$unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
$unionWheres = $unionBuilder->getRawBindings()['where'];

$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 $builder) use ($addCursorConditions, $column, $i) {
$addCursorConditions($builder, $column, $i + 1);
$unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $originalColumn, $i) {
$addCursorConditions($fourthBuilder, $column, $originalColumn, $i + 1);
});
}

$this->addBinding($unionWheres, 'union');
$this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
});
});
});
};

$addCursorConditions($this, null, 0);
$addCursorConditions($this, null, null, 0);
}

$this->limit($perPage + 1);
Expand Down
13 changes: 13 additions & 0 deletions src/Illuminate/Database/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class Builder implements BuilderContract
'getbindings',
'getconnection',
'getgrammar',
'getrawbindings',
'implode',
'insert',
'insertgetid',
Expand Down Expand Up @@ -1771,6 +1772,18 @@ public function withSavepointIfNeeded(Closure $scope): mixed
: $scope();
}

/**
* Get the Eloquent builder instances that are used in the union of the query.
*
* @return \Illuminate\Support\Collection
*/
protected function getUnionBuilders()
{
return isset($this->query->unions)
? collect($this->query->unions)->pluck('query')
: collect();
}

/**
* Get the underlying query builder instance.
*
Expand Down
12 changes: 12 additions & 0 deletions src/Illuminate/Database/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3918,6 +3918,18 @@ public function raw($value)
return $this->connection->raw($value);
}

/**
* Get the query builder instances that are used in the union of the query.
*
* @return \Illuminate\Support\Collection
*/
protected function getUnionBuilders()
{
return isset($this->unions)
? collect($this->unions)->pluck('query')
: collect();
}

/**
* Get the current query value bindings in a flattened array.
*
Expand Down
4 changes: 2 additions & 2 deletions src/Illuminate/Testing/TestResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ public function assertHeaderMissing($headerName)
public function assertLocation($uri)
{
PHPUnit::assertEquals(
app('url')->to($uri), app('url')->to($this->headers->get('Location'))
app('url')->to($uri), app('url')->to($this->headers->get('Location', ''))
);

return $this;
Expand All @@ -335,7 +335,7 @@ public function assertLocation($uri)
*/
public function assertDownload($filename = null)
{
$contentDisposition = explode(';', $this->headers->get('content-disposition'));
$contentDisposition = explode(';', $this->headers->get('content-disposition', ''));

if (trim($contentDisposition[0]) !== 'attachment') {
PHPUnit::fail(
Expand Down
156 changes: 152 additions & 4 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5306,7 +5306,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']);
Expand All @@ -5327,6 +5327,105 @@ public function testCursorPaginateWithUnionWheres()
]), $result);
}

public function testCursorPaginateWithMultipleUnionsAndMultipleWheres()
{
$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')->where('extra', 'first'));
$builder->union($this->getBuilder()->select('id', 'created_at')->selectRaw("'podcast' as type")->from('podcasts')->where('extra', 'second'));
$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' => 'podcasts'],
]);

$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 ("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']);

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 testCursorPaginateWithUnionMultipleWheresMultipleOrders()
{
$ts = now()->toDateTimeString();

$perPage = 16;
$columns = ['id', 'created_at', 'type'];
$cursorName = 'cursor-name';
$cursor = new Cursor(['id' => 1, 'created_at' => $ts, 'type' => 'news']);
$builder = $this->getMockQueryBuilder();
$builder->select('id', 'start_time as created_at', 'type')->from('videos')->where('extra', 'first');
$builder->union($this->getBuilder()->select('id', 'created_at', 'type')->from('news')->where('extra', 'second'));
$builder->union($this->getBuilder()->select('id', 'created_at', 'type')->from('podcasts')->where('extra', 'third'));
$builder->orderBy('id')->orderByDesc('created_at')->orderBy('type');

$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()->addDay(), 'type' => 'video'],
['id' => 1, 'created_at' => now(), 'type' => 'news'],
['id' => 1, 'created_at' => now(), 'type' => 'podcast'],
['id' => 2, 'created_at' => now(), 'type' => 'podcast'],
]);

$builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) {
$this->assertEquals(
'(select "id", "start_time" as "created_at", "type" from "videos" where "extra" = ? and ("id" > ? or ("id" = ? and ("start_time" < ? or ("start_time" = ? and ("type" > ?)))))) union (select "id", "created_at", "type" from "news" where "extra" = ? and ("id" > ? or ("id" = ? and ("start_time" < ? or ("start_time" = ? and ("type" > ?)))))) union (select "id", "created_at", "type" from "podcasts" where "extra" = ? and ("id" > ? or ("id" = ? and ("start_time" < ? or ("start_time" = ? and ("type" > ?)))))) order by "id" asc, "created_at" desc, "type" asc limit 17',
$builder->toSql());
$this->assertEquals(['first', 1, 1, $ts, $ts, 'news'], $builder->bindings['where']);
$this->assertEquals(['second', 1, 1, $ts, $ts, 'news', 'third', 1, 1, $ts, $ts, 'news'], $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' => ['id', 'created_at', 'type'],
]), $result);
}

public function testCursorPaginateWithUnionWheresWithRawOrderExpression()
{
$ts = now()->toDateTimeString();
Expand All @@ -5353,7 +5452,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']);
Expand Down Expand Up @@ -5400,7 +5499,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']);
Expand Down Expand Up @@ -5447,7 +5546,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']);
Expand All @@ -5468,6 +5567,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();
Expand Down
Loading

0 comments on commit 43e4a9c

Please sign in to comment.