v0.0.0 Initial Commit

This commit is contained in:
Anders
2021-06-01 10:17:03 +02:00
commit 78df7dd754
3412 changed files with 354795 additions and 0 deletions
+378
View File
@@ -0,0 +1,378 @@
<?php
namespace Illuminate\Database\Concerns;
use Illuminate\Container\Container;
use Illuminate\Database\MultipleRecordsFoundException;
use Illuminate\Database\RecordsNotFoundException;
use Illuminate\Pagination\CursorPaginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use InvalidArgumentException;
use RuntimeException;
trait BuildsQueries
{
/**
* Chunk the results of the query.
*
* @param int $count
* @param callable $callback
* @return bool
*/
public function chunk($count, callable $callback)
{
$this->enforceOrderBy();
$page = 1;
do {
// We'll execute the query for the given page and get the results. If there are
// no results we can just break and return from here. When there are results
// we will call the callback with the current chunk of these results here.
$results = $this->forPage($page, $count)->get();
$countResults = $results->count();
if ($countResults == 0) {
break;
}
// 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, $page) === false) {
return false;
}
unset($results);
$page++;
} while ($countResults == $count);
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)
{
return $this->chunk($count, function ($results) use ($callback) {
foreach ($results as $key => $value) {
if ($callback($value, $key) === false) {
return false;
}
}
});
}
/**
* Chunk the results of a query by comparing IDs.
*
* @param int $count
* @param callable $callback
* @param string|null $column
* @param string|null $alias
* @return bool
*/
public function chunkById($count, callable $callback, $column = null, $alias = null)
{
$column = $column ?? $this->defaultKeyName();
$alias = $alias ?? $column;
$lastId = null;
$page = 1;
do {
$clone = clone $this;
// We'll execute the query for the given page and get the results. If there are
// no results we can just break and return from here. When there are results
// we will call the callback with the current chunk of these results here.
$results = $clone->forPageAfterId($count, $lastId, $column)->get();
$countResults = $results->count();
if ($countResults == 0) {
break;
}
// 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, $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;
}
/**
* Execute a callback over each item while chunking by ID.
*
* @param callable $callback
* @param int $count
* @param string|null $column
* @param string|null $alias
* @return bool
*/
public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
{
return $this->chunkById($count, function ($results, $page) use ($callback, $count) {
foreach ($results as $key => $value) {
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 $count
* @param string|null $column
* @param string|null $alias
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
{
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) {
$lastId = null;
while (true) {
$clone = clone $this;
$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.
*
* @param array|string $columns
* @return \Illuminate\Database\Eloquent\Model|object|static|null
*/
public function first($columns = ['*'])
{
return $this->take(1)->get($columns)->first();
}
/**
* Execute the query and get the first result if it's the sole matching record.
*
* @param array|string $columns
* @return \Illuminate\Database\Eloquent\Model|object|static|null
*
* @throws \Illuminate\Database\RecordsNotFoundException
* @throws \Illuminate\Database\MultipleRecordsFoundException
*/
public function sole($columns = ['*'])
{
$result = $this->take(2)->get($columns);
if ($result->isEmpty()) {
throw new RecordsNotFoundException;
}
if ($result->count() > 1) {
throw new MultipleRecordsFoundException;
}
return $result->first();
}
/**
* Apply the callback's query changes if the given "value" is true.
*
* @param mixed $value
* @param callable $callback
* @param callable|null $default
* @return mixed|$this
*/
public function when($value, $callback, $default = null)
{
if ($value) {
return $callback($this, $value) ?: $this;
} elseif ($default) {
return $default($this, $value) ?: $this;
}
return $this;
}
/**
* Pass the query to a given callback.
*
* @param callable $callback
* @return $this
*/
public function tap($callback)
{
return $this->when(true, $callback);
}
/**
* Apply the callback's query changes if the given "value" is false.
*
* @param mixed $value
* @param callable $callback
* @param callable|null $default
* @return mixed|$this
*/
public function unless($value, $callback, $default = null)
{
if (! $value) {
return $callback($this, $value) ?: $this;
} elseif ($default) {
return $default($this, $value) ?: $this;
}
return $this;
}
/**
* Create a new length-aware paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param int $total
* @param int $perPage
* @param int $currentPage
* @param array $options
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
protected function paginator($items, $total, $perPage, $currentPage, $options)
{
return Container::getInstance()->makeWith(LengthAwarePaginator::class, compact(
'items', 'total', 'perPage', 'currentPage', 'options'
));
}
/**
* Create a new simple paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param int $perPage
* @param int $currentPage
* @param array $options
* @return \Illuminate\Pagination\Paginator
*/
protected function simplePaginator($items, $perPage, $currentPage, $options)
{
return Container::getInstance()->makeWith(Paginator::class, compact(
'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\Paginator
*/
protected function cursorPaginator($items, $perPage, $cursor, $options)
{
return Container::getInstance()->makeWith(CursorPaginator::class, compact(
'items', 'perPage', 'cursor', 'options'
));
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace Illuminate\Database\Concerns;
use Illuminate\Support\Collection;
trait ExplainsQueries
{
/**
* Explains the query.
*
* @return \Illuminate\Support\Collection
*/
public function explain()
{
$sql = $this->toSql();
$bindings = $this->getBindings();
$explanation = $this->getConnection()->select('EXPLAIN '.$sql, $bindings);
return new Collection($explanation);
}
}
@@ -0,0 +1,334 @@
<?php
namespace Illuminate\Database\Concerns;
use Closure;
use RuntimeException;
use Throwable;
trait ManagesTransactions
{
/**
* Execute a Closure within a transaction.
*
* @param \Closure $callback
* @param int $attempts
* @return mixed
*
* @throws \Throwable
*/
public function transaction(Closure $callback, $attempts = 1)
{
for ($currentAttempt = 1; $currentAttempt <= $attempts; $currentAttempt++) {
$this->beginTransaction();
// We'll simply execute the given callback within a try / catch block and if we
// catch any exception we can rollback this transaction so that none of this
// gets actually persisted to a database or stored in a permanent fashion.
try {
$callbackResult = $callback($this);
}
// If we catch an exception we'll rollback this transaction and try again if we
// are not out of attempts. If we are out of attempts we will just throw the
// exception back out and let the developer handle an uncaught exceptions.
catch (Throwable $e) {
$this->handleTransactionException(
$e, $currentAttempt, $attempts
);
continue;
}
try {
if ($this->transactions == 1) {
$this->getPdo()->commit();
}
$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
);
continue;
}
$this->fireConnectionEvent('committed');
return $callbackResult;
}
}
/**
* Handle an exception encountered when running a transacted statement.
*
* @param \Throwable $e
* @param int $currentAttempt
* @param int $maxAttempts
* @return void
*
* @throws \Throwable
*/
protected function handleTransactionException(Throwable $e, $currentAttempt, $maxAttempts)
{
// On a deadlock, MySQL rolls back the entire transaction so we can't just
// retry the query. We have to throw this exception all the way out and
// let the developer handle it in another way. We will decrement too.
if ($this->causedByConcurrencyError($e) &&
$this->transactions > 1) {
$this->transactions--;
optional($this->transactionsManager)->rollback(
$this->getName(), $this->transactions
);
throw $e;
}
// If there was an exception we will rollback this transaction and then we
// can check if we have exceeded the maximum attempt count for this and
// if we haven't we will return and try this query again in our loop.
$this->rollBack();
if ($this->causedByConcurrencyError($e) &&
$currentAttempt < $maxAttempts) {
return;
}
throw $e;
}
/**
* Start a new database transaction.
*
* @return void
*
* @throws \Throwable
*/
public function beginTransaction()
{
$this->createTransaction();
$this->transactions++;
optional($this->transactionsManager)->begin(
$this->getName(), $this->transactions
);
$this->fireConnectionEvent('beganTransaction');
}
/**
* Create a transaction within the database.
*
* @return void
*
* @throws \Throwable
*/
protected function createTransaction()
{
if ($this->transactions == 0) {
$this->reconnectIfMissingConnection();
try {
$this->getPdo()->beginTransaction();
} catch (Throwable $e) {
$this->handleBeginTransactionException($e);
}
} elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
$this->createSavepoint();
}
}
/**
* Create a save point within the database.
*
* @return void
*
* @throws \Throwable
*/
protected function createSavepoint()
{
$this->getPdo()->exec(
$this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
);
}
/**
* Handle an exception from a transaction beginning.
*
* @param \Throwable $e
* @return void
*
* @throws \Throwable
*/
protected function handleBeginTransactionException(Throwable $e)
{
if ($this->causedByLostConnection($e)) {
$this->reconnect();
$this->getPdo()->beginTransaction();
} else {
throw $e;
}
}
/**
* Commit the active database transaction.
*
* @return void
*
* @throws \Throwable
*/
public function commit()
{
if ($this->transactions == 1) {
$this->getPdo()->commit();
}
$this->transactions = max(0, $this->transactions - 1);
if ($this->transactions == 0) {
optional($this->transactionsManager)->commit($this->getName());
}
$this->fireConnectionEvent('committed');
}
/**
* Handle an exception encountered when committing a transaction.
*
* @param \Throwable $e
* @param int $currentAttempt
* @param int $maxAttempts
* @return void
*
* @throws \Throwable
*/
protected function handleCommitTransactionException(Throwable $e, $currentAttempt, $maxAttempts)
{
$this->transactions = max(0, $this->transactions - 1);
if ($this->causedByConcurrencyError($e) &&
$currentAttempt < $maxAttempts) {
return;
}
if ($this->causedByLostConnection($e)) {
$this->transactions = 0;
}
throw $e;
}
/**
* Rollback the active database transaction.
*
* @param int|null $toLevel
* @return void
*
* @throws \Throwable
*/
public function rollBack($toLevel = null)
{
// We allow developers to rollback to a certain transaction level. We will verify
// that this given transaction level is valid before attempting to rollback to
// that level. If it's not we will just return out and not attempt anything.
$toLevel = is_null($toLevel)
? $this->transactions - 1
: $toLevel;
if ($toLevel < 0 || $toLevel >= $this->transactions) {
return;
}
// Next, we will actually perform this rollback within this database and fire the
// rollback event. We will also set the current transaction level to the given
// level that was passed into this method so it will be right from here out.
try {
$this->performRollBack($toLevel);
} catch (Throwable $e) {
$this->handleRollBackException($e);
}
$this->transactions = $toLevel;
optional($this->transactionsManager)->rollback(
$this->getName(), $this->transactions
);
$this->fireConnectionEvent('rollingBack');
}
/**
* Perform a rollback within the database.
*
* @param int $toLevel
* @return void
*
* @throws \Throwable
*/
protected function performRollBack($toLevel)
{
if ($toLevel == 0) {
$this->getPdo()->rollBack();
} elseif ($this->queryGrammar->supportsSavepoints()) {
$this->getPdo()->exec(
$this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
);
}
}
/**
* Handle an exception from a rollback.
*
* @param \Throwable $e
* @return void
*
* @throws \Throwable
*/
protected function handleRollBackException(Throwable $e)
{
if ($this->causedByLostConnection($e)) {
$this->transactions = 0;
optional($this->transactionsManager)->rollback(
$this->getName(), $this->transactions
);
}
throw $e;
}
/**
* Get the number of active transactions.
*
* @return int
*/
public function transactionLevel()
{
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.');
}
}