Composer Version Change
This commit is contained in:
+315
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Driver;
|
||||
|
||||
use Cake\Database\Expression\ComparisonExpression;
|
||||
use Cake\Database\Expression\IdentifierExpression;
|
||||
use Cake\Database\IdentifierQuoter;
|
||||
use Cake\Database\Query;
|
||||
use Closure;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Sql dialect trait
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait SqlDialectTrait
|
||||
{
|
||||
/**
|
||||
* Quotes a database identifier (a column name, table name, etc..) to
|
||||
* be used safely in queries without the risk of using reserved words
|
||||
*
|
||||
* @param string $identifier The identifier to quote.
|
||||
* @return string
|
||||
*/
|
||||
public function quoteIdentifier(string $identifier): string
|
||||
{
|
||||
$identifier = trim($identifier);
|
||||
|
||||
if ($identifier === '*' || $identifier === '') {
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
// string
|
||||
if (preg_match('/^[\w-]+$/u', $identifier)) {
|
||||
return $this->_startQuote . $identifier . $this->_endQuote;
|
||||
}
|
||||
|
||||
// string.string
|
||||
if (preg_match('/^[\w-]+\.[^ \*]*$/u', $identifier)) {
|
||||
$items = explode('.', $identifier);
|
||||
|
||||
return $this->_startQuote . implode($this->_endQuote . '.' . $this->_startQuote, $items) . $this->_endQuote;
|
||||
}
|
||||
|
||||
// string.*
|
||||
if (preg_match('/^[\w-]+\.\*$/u', $identifier)) {
|
||||
return $this->_startQuote . str_replace('.*', $this->_endQuote . '.*', $identifier);
|
||||
}
|
||||
|
||||
// Functions
|
||||
if (preg_match('/^([\w-]+)\((.*)\)$/', $identifier, $matches)) {
|
||||
return $matches[1] . '(' . $this->quoteIdentifier($matches[2]) . ')';
|
||||
}
|
||||
|
||||
// Alias.field AS thing
|
||||
if (preg_match('/^([\w-]+(\.[\w\s-]+|\(.*\))*)\s+AS\s*([\w-]+)$/ui', $identifier, $matches)) {
|
||||
return $this->quoteIdentifier($matches[1]) . ' AS ' . $this->quoteIdentifier($matches[3]);
|
||||
}
|
||||
|
||||
// string.string with spaces
|
||||
if (preg_match('/^([\w-]+\.[\w][\w\s-]*[\w])(.*)/u', $identifier, $matches)) {
|
||||
$items = explode('.', $matches[1]);
|
||||
$field = implode($this->_endQuote . '.' . $this->_startQuote, $items);
|
||||
|
||||
return $this->_startQuote . $field . $this->_endQuote . $matches[2];
|
||||
}
|
||||
|
||||
if (preg_match('/^[\w\s-]*[\w-]+/u', $identifier)) {
|
||||
return $this->_startQuote . $identifier . $this->_endQuote;
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a callable function that will be used to transform a passed Query object.
|
||||
* This function, in turn, will return an instance of a Query object that has been
|
||||
* transformed to accommodate any specificities of the SQL dialect in use.
|
||||
*
|
||||
* @param string $type the type of query to be transformed
|
||||
* (select, insert, update, delete)
|
||||
* @return \Closure
|
||||
*/
|
||||
public function queryTranslator(string $type): Closure
|
||||
{
|
||||
return function ($query) use ($type) {
|
||||
if ($this->isAutoQuotingEnabled()) {
|
||||
$query = (new IdentifierQuoter($this))->quote($query);
|
||||
}
|
||||
|
||||
/** @var \Cake\ORM\Query $query */
|
||||
$query = $this->{'_' . $type . 'QueryTranslator'}($query);
|
||||
$translators = $this->_expressionTranslators();
|
||||
if (!$translators) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$query->traverseExpressions(function ($expression) use ($translators, $query): void {
|
||||
foreach ($translators as $class => $method) {
|
||||
if ($expression instanceof $class) {
|
||||
$this->{$method}($expression, $query);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $query;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an associative array of methods that will transform Expression
|
||||
* objects to conform with the specific SQL dialect. Keys are class names
|
||||
* and values a method in this class.
|
||||
*
|
||||
* @psalm-return array<class-string, string>
|
||||
* @return array<string>
|
||||
*/
|
||||
protected function _expressionTranslators(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translation steps to select queries.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to translate
|
||||
* @return \Cake\Database\Query The modified query
|
||||
*/
|
||||
protected function _selectQueryTranslator(Query $query): Query
|
||||
{
|
||||
return $this->_transformDistinct($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the passed query after rewriting the DISTINCT clause, so that drivers
|
||||
* that do not support the "ON" part can provide the actual way it should be done
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to be transformed
|
||||
* @return \Cake\Database\Query
|
||||
*/
|
||||
protected function _transformDistinct(Query $query): Query
|
||||
{
|
||||
if (is_array($query->clause('distinct'))) {
|
||||
$query->group($query->clause('distinct'), true);
|
||||
$query->distinct(false);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translation steps to delete queries.
|
||||
*
|
||||
* Chops out aliases on delete query conditions as most database dialects do not
|
||||
* support aliases in delete queries. This also removes aliases
|
||||
* in table names as they frequently don't work either.
|
||||
*
|
||||
* We are intentionally not supporting deletes with joins as they have even poorer support.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to translate
|
||||
* @return \Cake\Database\Query The modified query
|
||||
*/
|
||||
protected function _deleteQueryTranslator(Query $query): Query
|
||||
{
|
||||
$hadAlias = false;
|
||||
$tables = [];
|
||||
foreach ($query->clause('from') as $alias => $table) {
|
||||
if (is_string($alias)) {
|
||||
$hadAlias = true;
|
||||
}
|
||||
$tables[] = $table;
|
||||
}
|
||||
if ($hadAlias) {
|
||||
$query->from($tables, true);
|
||||
}
|
||||
|
||||
if (!$hadAlias) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $this->_removeAliasesFromConditions($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translation steps to update queries.
|
||||
*
|
||||
* Chops out aliases on update query conditions as not all database dialects do support
|
||||
* aliases in update queries.
|
||||
*
|
||||
* Just like for delete queries, joins are currently not supported for update queries.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to translate
|
||||
* @return \Cake\Database\Query The modified query
|
||||
*/
|
||||
protected function _updateQueryTranslator(Query $query): Query
|
||||
{
|
||||
return $this->_removeAliasesFromConditions($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes aliases from the `WHERE` clause of a query.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to process.
|
||||
* @return \Cake\Database\Query The modified query.
|
||||
* @throws \RuntimeException In case the processed query contains any joins, as removing
|
||||
* aliases from the conditions can break references to the joined tables.
|
||||
*/
|
||||
protected function _removeAliasesFromConditions(Query $query): Query
|
||||
{
|
||||
if ($query->clause('join')) {
|
||||
throw new RuntimeException(
|
||||
'Aliases are being removed from conditions for UPDATE/DELETE queries, ' .
|
||||
'this can break references to joined tables.'
|
||||
);
|
||||
}
|
||||
|
||||
$conditions = $query->clause('where');
|
||||
if ($conditions) {
|
||||
$conditions->traverse(function ($expression) {
|
||||
if ($expression instanceof ComparisonExpression) {
|
||||
$field = $expression->getField();
|
||||
if (
|
||||
is_string($field) &&
|
||||
strpos($field, '.') !== false
|
||||
) {
|
||||
[, $unaliasedField] = explode('.', $field, 2);
|
||||
$expression->setField($unaliasedField);
|
||||
}
|
||||
|
||||
return $expression;
|
||||
}
|
||||
|
||||
if ($expression instanceof IdentifierExpression) {
|
||||
$identifier = $expression->getIdentifier();
|
||||
if (strpos($identifier, '.') !== false) {
|
||||
[, $unaliasedIdentifier] = explode('.', $identifier, 2);
|
||||
$expression->setIdentifier($unaliasedIdentifier);
|
||||
}
|
||||
|
||||
return $expression;
|
||||
}
|
||||
|
||||
return $expression;
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translation steps to insert queries.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to translate
|
||||
* @return \Cake\Database\Query The modified query
|
||||
*/
|
||||
protected function _insertQueryTranslator(Query $query): Query
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SQL snippet for creating a new transaction savepoint
|
||||
*
|
||||
* @param string|int $name save point name
|
||||
* @return string
|
||||
*/
|
||||
public function savePointSQL($name): string
|
||||
{
|
||||
return 'SAVEPOINT LEVEL' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SQL snippet for releasing a previously created save point
|
||||
*
|
||||
* @param string|int $name save point name
|
||||
* @return string
|
||||
*/
|
||||
public function releaseSavePointSQL($name): string
|
||||
{
|
||||
return 'RELEASE SAVEPOINT LEVEL' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SQL snippet for rollbacking a previously created save point
|
||||
*
|
||||
* @param string|int $name save point name
|
||||
* @return string
|
||||
*/
|
||||
public function rollbackSavePointSQL($name): string
|
||||
{
|
||||
return 'ROLLBACK TO SAVEPOINT LEVEL' . $name;
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Driver\SqlDialectTrait',
|
||||
'Cake\Database\SqlDialectTrait'
|
||||
);
|
||||
// phpcs:enable
|
||||
Reference in New Issue
Block a user