This commit is contained in:
Paolo A
2024-08-13 13:44:16 +00:00
parent 1bbb23088d
commit e796d76612
4001 changed files with 30101 additions and 40075 deletions

View File

@@ -3,11 +3,23 @@
namespace Illuminate\Database\Concerns;
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\MultipleRecordsFoundException;
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\Traits\Conditionable;
use InvalidArgumentException;
use RuntimeException;
trait BuildsQueries
{
use Conditionable;
/**
* Chunk the results of the query.
*
@@ -48,12 +60,34 @@ trait BuildsQueries
return true;
}
/**
* Run a map over each item while chunking.
*
* @param callable $callback
* @param int $count
* @return \Illuminate\Support\Collection
*/
public function chunkMap(callable $callback, $count = 1000)
{
$collection = Collection::make();
$this->chunk($count, function ($items) use ($collection, $callback) {
$items->each(function ($item) use ($collection, $callback) {
$collection->push($callback($item));
});
});
return $collection;
}
/**
* Execute a callback over each item while chunking.
*
* @param callable $callback
* @param int $count
* @return bool
*
* @throws \RuntimeException
*/
public function each(callable $callback, $count = 1000)
{
@@ -83,6 +117,8 @@ trait BuildsQueries
$lastId = null;
$page = 1;
do {
$clone = clone $this;
@@ -100,13 +136,19 @@ trait BuildsQueries
// On each chunk result set, we will pass them to the callback and then let the
// developer take care of everything within the callback, which allows us to
// keep the memory low for spinning through large result sets for working.
if ($callback($results) === false) {
if ($callback($results, $page) === false) {
return false;
}
$lastId = $results->last()->{$alias};
if ($lastId === null) {
throw new RuntimeException("The chunkById operation was aborted because the [{$alias}] column is not present in the query result.");
}
unset($results);
$page++;
} while ($countResults == $count);
return true;
@@ -123,15 +165,124 @@ trait BuildsQueries
*/
public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
{
return $this->chunkById($count, function ($results) use ($callback) {
return $this->chunkById($count, function ($results, $page) use ($callback, $count) {
foreach ($results as $key => $value) {
if ($callback($value, $key) === false) {
if ($callback($value, (($page - 1) * $count) + $key) === false) {
return false;
}
}
}, $column, $alias);
}
/**
* Query lazily, by chunks of the given size.
*
* @param int $chunkSize
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
public function lazy($chunkSize = 1000)
{
if ($chunkSize < 1) {
throw new InvalidArgumentException('The chunk size should be at least 1');
}
$this->enforceOrderBy();
return LazyCollection::make(function () use ($chunkSize) {
$page = 1;
while (true) {
$results = $this->forPage($page++, $chunkSize)->get();
foreach ($results as $result) {
yield $result;
}
if ($results->count() < $chunkSize) {
return;
}
}
});
}
/**
* Query lazily, by chunking the results of a query by comparing IDs.
*
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
{
return $this->orderedLazyById($chunkSize, $column, $alias);
}
/**
* Query lazily, by chunking the results of a query by comparing IDs in descending order.
*
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null)
{
return $this->orderedLazyById($chunkSize, $column, $alias, true);
}
/**
* Query lazily, by chunking the results of a query by comparing IDs in a given order.
*
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @param bool $descending
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
protected function orderedLazyById($chunkSize = 1000, $column = null, $alias = null, $descending = false)
{
if ($chunkSize < 1) {
throw new InvalidArgumentException('The chunk size should be at least 1');
}
$column = $column ?? $this->defaultKeyName();
$alias = $alias ?? $column;
return LazyCollection::make(function () use ($chunkSize, $column, $alias, $descending) {
$lastId = null;
while (true) {
$clone = clone $this;
if ($descending) {
$results = $clone->forPageBeforeId($chunkSize, $lastId, $column)->get();
} else {
$results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get();
}
foreach ($results as $result) {
yield $result;
}
if ($results->count() < $chunkSize) {
return;
}
$lastId = $results->last()->{$alias};
}
});
}
/**
* Execute the query and get the first result.
*
@@ -144,52 +295,143 @@ trait BuildsQueries
}
/**
* Apply the callback's query changes if the given "value" is true.
* Execute the query and get the first result if it's the sole matching record.
*
* @param mixed $value
* @param callable $callback
* @param callable|null $default
* @return mixed|$this
* @param array|string $columns
* @return \Illuminate\Database\Eloquent\Model|object|static|null
*
* @throws \Illuminate\Database\RecordsNotFoundException
* @throws \Illuminate\Database\MultipleRecordsFoundException
*/
public function when($value, $callback, $default = null)
public function sole($columns = ['*'])
{
if ($value) {
return $callback($this, $value) ?: $this;
} elseif ($default) {
return $default($this, $value) ?: $this;
$result = $this->take(2)->get($columns);
if ($result->isEmpty()) {
throw new RecordsNotFoundException;
}
return $this;
if ($result->count() > 1) {
throw new MultipleRecordsFoundException;
}
return $result->first();
}
/**
* Pass the query to a given callback.
* Paginate the given query using a cursor paginator.
*
* @param callable $callback
* @return $this
* @param int $perPage
* @param array $columns
* @param string $cursorName
* @param \Illuminate\Pagination\Cursor|string|null $cursor
* @return \Illuminate\Contracts\Pagination\CursorPaginator
*/
public function tap($callback)
protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
{
return $this->when(true, $callback);
if (! $cursor instanceof Cursor) {
$cursor = is_string($cursor)
? Cursor::fromEncoded($cursor)
: CursorPaginator::resolveCurrentCursor($cursorName, $cursor);
}
$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();
if (! is_null($previousColumn)) {
$builder->where(
$this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
'=',
$cursor->parameter($previousColumn)
);
$unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) {
$unionBuilder->where(
$this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
'=',
$cursor->parameter($previousColumn)
);
$this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
});
}
$builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) {
['column' => $column, 'direction' => $direction] = $orders[$i];
$builder->where(
$this->getOriginalColumnNameForCursorPagination($this, $column),
$direction === 'asc' ? '>' : '<',
$cursor->parameter($column)
);
if ($i < $orders->count() - 1) {
$builder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) {
$addCursorConditions($builder, $column, $i + 1);
});
}
$unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
$unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
$unionBuilder->where(
$this->getOriginalColumnNameForCursorPagination($this, $column),
$direction === 'asc' ? '>' : '<',
$cursor->parameter($column)
);
if ($i < $orders->count() - 1) {
$unionBuilder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) {
$addCursorConditions($builder, $column, $i + 1);
});
}
$this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
});
});
});
};
$addCursorConditions($this, null, 0);
}
$this->limit($perPage + 1);
return $this->cursorPaginator($this->get($columns), $perPage, $cursor, [
'path' => Paginator::resolveCurrentPath(),
'cursorName' => $cursorName,
'parameters' => $orders->pluck('column')->toArray(),
]);
}
/**
* Apply the callback's query changes if the given "value" is false.
* Get the original column name of the given column, without any aliasing.
*
* @param mixed $value
* @param callable $callback
* @param callable|null $default
* @return mixed|$this
* @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder
* @param string $parameter
* @return string
*/
public function unless($value, $callback, $default = null)
protected function getOriginalColumnNameForCursorPagination($builder, string $parameter)
{
if (! $value) {
return $callback($this, $value) ?: $this;
} elseif ($default) {
return $default($this, $value) ?: $this;
$columns = $builder instanceof Builder ? $builder->getQuery()->columns : $builder->columns;
if (! is_null($columns)) {
foreach ($columns as $column) {
if (($position = stripos($column, ' as ')) !== false) {
$as = substr($column, $position, 4);
[$original, $alias] = explode($as, $column);
if ($parameter === $alias) {
return $original;
}
}
}
}
return $this;
return $parameter;
}
/**
@@ -224,4 +466,31 @@ trait BuildsQueries
'items', 'perPage', 'currentPage', 'options'
));
}
/**
* Create a new cursor paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param int $perPage
* @param \Illuminate\Pagination\Cursor $cursor
* @param array $options
* @return \Illuminate\Pagination\CursorPaginator
*/
protected function cursorPaginator($items, $perPage, $cursor, $options)
{
return Container::getInstance()->makeWith(CursorPaginator::class, compact(
'items', 'perPage', 'cursor', 'options'
));
}
/**
* Pass the query to a given callback.
*
* @param callable $callback
* @return $this|mixed
*/
public function tap($callback)
{
return $this->when(true, $callback);
}
}

View File

@@ -3,6 +3,7 @@
namespace Illuminate\Database\Concerns;
use Closure;
use RuntimeException;
use Throwable;
trait ManagesTransactions
@@ -45,6 +46,10 @@ trait ManagesTransactions
}
$this->transactions = max(0, $this->transactions - 1);
if ($this->transactions == 0) {
optional($this->transactionsManager)->commit($this->getName());
}
} catch (Throwable $e) {
$this->handleCommitTransactionException(
$e, $currentAttempt, $attempts
@@ -78,6 +83,10 @@ trait ManagesTransactions
$this->transactions > 1) {
$this->transactions--;
optional($this->transactionsManager)->rollback(
$this->getName(), $this->transactions
);
throw $e;
}
@@ -107,6 +116,10 @@ trait ManagesTransactions
$this->transactions++;
optional($this->transactionsManager)->begin(
$this->getName(), $this->transactions
);
$this->fireConnectionEvent('beganTransaction');
}
@@ -180,6 +193,10 @@ trait ManagesTransactions
$this->transactions = max(0, $this->transactions - 1);
if ($this->transactions == 0) {
optional($this->transactionsManager)->commit($this->getName());
}
$this->fireConnectionEvent('committed');
}
@@ -241,6 +258,10 @@ trait ManagesTransactions
$this->transactions = $toLevel;
optional($this->transactionsManager)->rollback(
$this->getName(), $this->transactions
);
$this->fireConnectionEvent('rollingBack');
}
@@ -275,6 +296,10 @@ trait ManagesTransactions
{
if ($this->causedByLostConnection($e)) {
$this->transactions = 0;
optional($this->transactionsManager)->rollback(
$this->getName(), $this->transactions
);
}
throw $e;
@@ -289,4 +314,21 @@ trait ManagesTransactions
{
return $this->transactions;
}
/**
* Execute the callback after a transaction commits.
*
* @param callable $callback
* @return void
*
* @throws \RuntimeException
*/
public function afterCommit($callback)
{
if ($this->transactionsManager) {
return $this->transactionsManager->addCallback($callback);
}
throw new RuntimeException('Transactions Manager has not been set.');
}
}